private static string Payload(JwtScopes scope, string orgCode, string roleProfileId, string asid, string endpoint, string tokenOrigin, string resource, DateTime?tokenStart) { var start = tokenStart.HasValue ? tokenStart.Value : DateTime.UtcNow; var claims = new List <Claim>(); var exp = start.AddMinutes(5); var iat = start; claims.Add(new Claim(FhirConstants.JwtClientSysIssuer, tokenOrigin, ClaimValueTypes.String)); claims.Add(new Claim(FhirConstants.JwtIndOrSysIdentifier, $"{FhirConstants.SystemSdsRole}|{roleProfileId}", ClaimValueTypes.String)); claims.Add(new Claim(FhirConstants.JwtEndpointUrl, endpoint, ClaimValueTypes.String)); claims.Add(new Claim(FhirConstants.JwtExpiration, EpochTime.GetIntDate(exp).ToString(), ClaimValueTypes.Integer64)); claims.Add(new Claim(FhirConstants.JwtIssued, EpochTime.GetIntDate(iat).ToString(), ClaimValueTypes.Integer64)); claims.Add(new Claim(FhirConstants.JwtReasonForRequest, "directcare", ClaimValueTypes.String)); claims.Add(new Claim(FhirConstants.JwtScope, $"patient/{resource}.{scope.ToString().ToLowerInvariant()}", ClaimValueTypes.String)); claims.Add(new Claim(FhirConstants.JwtRequestingSystem, $"{FhirConstants.SystemASID}|{asid}", ClaimValueTypes.String)); // Should this not be JsonClaimValueTypes.Json claims.Add(new Claim(FhirConstants.JwtRequestingOrganization, $"{FhirConstants.SystemOrgCode}|{orgCode}", ClaimValueTypes.String)); // Should this not be JsonClaimValueTypes.Json claims.Add(new Claim(FhirConstants.JwtRequestingUser, $"{FhirConstants.SystemSdsRole}|{roleProfileId}", ClaimValueTypes.String)); // Should this not be JsonClaimValueTypes.Json return(new JwtPayload(claims).Base64UrlEncode()); }
public Response IsValid(string jwt, JwtScopes reqScope, DateTime?tokenIssued) { var now = tokenIssued ?? DateTime.UtcNow; var parsedJwt = ParseJwt(jwt); if (!parsedJwt.Success) { return(parsedJwt); } var claims = parsedJwt.Data; // ### iss var iss = claims.FirstOrDefault(x => x.Key.Equals(FhirConstants.JwtClientSysIssuer)); if (string.IsNullOrWhiteSpace(iss.Value)) { return(new Response(BaseErrorMessage(FhirConstants.JwtClientSysIssuer))); } // ### aud var aud = claims.FirstOrDefault(x => x.Key.Equals(FhirConstants.JwtEndpointUrl)); if (string.IsNullOrWhiteSpace(aud.Value)) { return(new Response(BaseErrorMessage(FhirConstants.JwtEndpointUrl))); } // ### iat basic var iat = claims.FirstOrDefault(x => x.Key.Equals(FhirConstants.JwtIssued)); long iatVal; if (!long.TryParse(iat.Value, out iatVal)) { return(new Response(BaseErrorMessage(FhirConstants.JwtIssued))); } // ### exp basic var exp = claims.FirstOrDefault(x => x.Key.Equals(FhirConstants.JwtExpiration)); long expVal; if (!long.TryParse(exp.Value, out expVal)) { return(new Response(BaseErrorMessage(FhirConstants.JwtExpiration))); } // ### iat and exp checks //Temporarily turn off as NRLS API does not validate these //var issuedDt = EpochTime.DateTime(iatVal); //var expireDt = EpochTime.DateTime(expVal); //if (issuedDt.AddMinutes(5) != expireDt ) //{ // return new Response($"exp {exp.Value} must be 5 minutes greater than iat {iat.Value}"); //} //if (issuedDt > now || expireDt < now) //{ // return new Response($"iat {iat.Value} must be in the past or now and exp {exp.Value} must be in the future or now"); //} // ### sub var subClaim = CheckSub(claims); if (!subClaim.Success) { return(subClaim); } // ### requesting_User var userClaim = CheckUser(claims, subClaim.Message); if (!userClaim.Success) { return(userClaim); } // ### requesting_system var reqSys = claims.FirstOrDefault(x => x.Key.Equals(FhirConstants.JwtRequestingSystem)); if (string.IsNullOrEmpty(reqSys.Value)) { return(new Response(BaseErrorMessage(FhirConstants.JwtRequestingSystem))); } // check reqSys matches sub if this is a system type i.e. if no user claim if (string.IsNullOrEmpty(userClaim.Message) && reqSys.Value != subClaim.Message) { return(new Response($"requesting_system ({reqSys.Value}) and sub ({subClaim.Message}) claim's values must match")); } var fromAsid = reqSys.Value.Replace($"{FhirConstants.SystemASID}|", ""); if (!reqSys.Value.StartsWith(FhirConstants.SystemASID) || string.IsNullOrWhiteSpace(fromAsid)) { return(new Response($"requesting_system ({reqSys.Value}) must be of the form [{FhirConstants.SystemASID}|[ASID]]")); } var fromAsidMap = _sdsService.GetFor(fromAsid); if (fromAsidMap == null) { return(new Response($"The ASID defined in the requesting_system ({fromAsid}) is unknown")); } // ### reason_for_request var resForReq = claims.FirstOrDefault(x => x.Key.Equals(FhirConstants.JwtReasonForRequest)); if (string.IsNullOrWhiteSpace(resForReq.Value)) { return(new Response(BaseErrorMessage(FhirConstants.JwtReasonForRequest))); } else if (resForReq.Value != "directcare") { return(new Response($"reason_for_request ({resForReq.Value}) must be 'directcare'")); } // ### scope var scope = claims.FirstOrDefault(x => x.Key.Equals(FhirConstants.JwtScope)); var expScope = $"patient/DocumentReference.{reqScope.ToString().ToLowerInvariant()}"; if (string.IsNullOrWhiteSpace(scope.Value)) { return(new Response(BaseErrorMessage(FhirConstants.JwtScope))); } else if (scope.Value != expScope) // || !_validScopes.Contains(scope.Value) { //currently return(new Response($"scope ({scope.Value}) must match either 'patient/DocumentReference.read' or 'patient/DocumentReference.write'")); } // ### requesting_organization var reqOrg = claims.FirstOrDefault(x => x.Key.Equals(FhirConstants.JwtRequestingOrganization)); var orgCode = reqOrg.Value?.Replace($"{FhirConstants.SystemOrgCode}|", ""); if (string.IsNullOrWhiteSpace(reqOrg.Value)) { return(new Response(BaseErrorMessage(FhirConstants.JwtRequestingOrganization))); } if (!reqOrg.Value.StartsWith(FhirConstants.SystemOrgCode) || string.IsNullOrWhiteSpace(orgCode)) { return(new Response($"requesting_organisation ({reqOrg.Value}) must be of the form [{FhirConstants.SystemOrgCode}|[ODSCode]")); } if (_sdsService.GetFor(orgCode, null) == null) { return(new Response($"The ODS code defined in the requesting_organisation({orgCode}) is unknown")); } // ### requesting_organization against requesting_system checks if (fromAsidMap.OdsCode != orgCode) { return(new Response($"requesting_system ASID ({fromAsid}) is not associated with the requesting_organisation ODS code ({orgCode})")); } return(new Response(true)); }
public Response IsValid(string jwt, JwtScopes reqScope, DateTime?tokenIssued = null) { var now = tokenIssued ?? DateTime.UtcNow; if (string.IsNullOrEmpty(jwt)) { return(new Response("The Authorisation header must be supplied")); } if (jwt.StartsWith("Bearer ")) { jwt = jwt.Replace("Bearer ", ""); } #region Base JWT Checks //This should be a basic Base64UrlEncoded token var claimsHashItems = jwt.Split('.'); if (claimsHashItems.Count() != 3) { return(new Response("The JWT associated with the Authorisation header must have the 3 sections")); } var signature = claimsHashItems.Skip(2).Take(1).FirstOrDefault(); if (!string.IsNullOrEmpty(signature)) { return(new Response("The JWT associated with the Authorisation header must have the 3 sections")); } var claimsHash = claimsHashItems.Skip(1).Take(1).FirstOrDefault(); if (string.IsNullOrEmpty(claimsHash)) { return(new Response("The JWT associated with the Authorisation header must have the 3 sections")); } claimsHash = claimsHash.Replace('-', '+').Replace('_', '/'); var decoded = DecodePart(claimsHash); IDictionary <string, string> claims; try { claims = JsonConvert.DeserializeObject <IDictionary <string, string> >(decoded); } catch (JsonReaderException ex) { var errorMessage = "The Authorisation header must be supplied"; if (!string.IsNullOrWhiteSpace(ex.Path) && _validClaims.Contains(ex.Path)) { errorMessage = BaseErrorMessage(ex.Path); } return(new Response(errorMessage)); } catch (Exception ex) { return(new Response("The Authorisation header must be supplied")); } #endregion // ### iss var iss = claims.FirstOrDefault(x => x.Key.Equals(FhirConstants.JwtClientSysIssuer)); if (string.IsNullOrWhiteSpace(iss.Value)) { return(new Response(BaseErrorMessage(FhirConstants.JwtClientSysIssuer))); } // ### aud var aud = claims.FirstOrDefault(x => x.Key.Equals(FhirConstants.JwtEndpointUrl)); if (string.IsNullOrWhiteSpace(aud.Value)) { return(new Response(BaseErrorMessage(FhirConstants.JwtEndpointUrl))); } // ### iat basic var iat = claims.FirstOrDefault(x => x.Key.Equals(FhirConstants.JwtIssued)); long iatVal; if (!long.TryParse(iat.Value, out iatVal)) { return(new Response(BaseErrorMessage(FhirConstants.JwtIssued))); } // ### exp basic var exp = claims.FirstOrDefault(x => x.Key.Equals(FhirConstants.JwtExpiration)); long expVal; if (!long.TryParse(exp.Value, out expVal)) { return(new Response(BaseErrorMessage(FhirConstants.JwtExpiration))); } // ### iat and exp checks //Temporarily turn off as NRLS API does not validate these //var issuedDt = EpochTime.DateTime(iatVal); //var expireDt = EpochTime.DateTime(expVal); //if (issuedDt.AddMinutes(5) != expireDt ) //{ // return new Response($"exp {exp.Value} must be 5 minutes greater than iat {iat.Value}"); //} //if (issuedDt > now || expireDt < now) //{ // return new Response($"iat {iat.Value} must be in the past or now and exp {exp.Value} must be in the future or now"); //} // ### sub var sub = claims.FirstOrDefault(x => x.Key.Equals(FhirConstants.JwtIndOrSysIdentifier)); if (string.IsNullOrEmpty(sub.Value)) { return(new Response(BaseErrorMessage(FhirConstants.JwtIndOrSysIdentifier))); } // ### requesting_user var userRequired = sub.Value.StartsWith($"{FhirConstants.SystemSdsRole}|"); var reqUsr = claims.FirstOrDefault(x => x.Key.Equals(FhirConstants.JwtRequestingUser)); var reqSys = claims.FirstOrDefault(x => x.Key.Equals(FhirConstants.JwtRequestingSystem)); if (userRequired && string.IsNullOrEmpty(reqUsr.Value)) { return(new Response(BaseErrorMessage(FhirConstants.JwtRequestingUser))); } // check reqUser matches sub if reqUsr was supplied, i.e. if this is a user type request rather than an system type request if (!string.IsNullOrEmpty(reqUsr.Value) && reqUsr.Value != sub.Value) { return(new Response($"requesting_user ({reqUsr.Value}) and sub ({sub.Value}) claim's values must match")); } var userRoleProfileId = reqUsr.Value?.Replace($"{FhirConstants.SystemSdsRole}|", ""); if (!string.IsNullOrEmpty(reqUsr.Value) && (!reqUsr.Value.StartsWith(FhirConstants.SystemSdsRole) || string.IsNullOrWhiteSpace(userRoleProfileId))) { return(new Response($"requesting_user ({reqSys.Value}) must be of the form [{FhirConstants.SystemSdsRole}|[SDSRoleProfileID]]")); } // ### requesting_system if (string.IsNullOrEmpty(reqSys.Value)) { return(new Response(BaseErrorMessage(FhirConstants.JwtRequestingSystem))); } // check reqSys matches sub if this is a system type if (string.IsNullOrEmpty(reqUsr.Value) && reqSys.Value != sub.Value) { return(new Response($"requesting_system ({reqSys.Value}) and sub ({sub.Value}) claim's values must match")); } var fromAsid = reqSys.Value.Replace($"{FhirConstants.SystemASID}|", ""); if (!reqSys.Value.StartsWith(FhirConstants.SystemASID) || string.IsNullOrWhiteSpace(fromAsid)) { return(new Response($"requesting_system ({reqSys.Value}) must be of the form [{FhirConstants.SystemASID}|[ASID]]")); } var fromAsidMap = GetFromAsidMap(fromAsid); if (fromAsidMap == null) { return(new Response($"The ASID defined in the requesting_system ({fromAsid}) is unknown")); } // ### reason_for_request var resForReq = claims.FirstOrDefault(x => x.Key.Equals(FhirConstants.JwtReasonForRequest)); if (string.IsNullOrWhiteSpace(resForReq.Value)) { return(new Response(BaseErrorMessage(FhirConstants.JwtReasonForRequest))); } else if (resForReq.Value != "directcare") { return(new Response($"reason_for_request ({resForReq.Value}) must be 'directcare'")); } // ### scope var scope = claims.FirstOrDefault(x => x.Key.Equals(FhirConstants.JwtScope)); var expScope = $"patient/DocumentReference.{reqScope.ToString().ToLowerInvariant()}"; if (string.IsNullOrWhiteSpace(scope.Value)) { return(new Response(BaseErrorMessage(FhirConstants.JwtScope))); } else if (scope.Value != expScope) // || !_validScopes.Contains(scope.Value) { //currently return(new Response($"scope ({scope.Value}) must match either 'patient/DocumentReference.read' or 'patient/DocumentReference.write'")); } // ### requesting_organization var reqOrg = claims.FirstOrDefault(x => x.Key.Equals(FhirConstants.JwtRequestingOrganization)); var orgCode = reqOrg.Value?.Replace($"{FhirConstants.SystemOrgCode}|", ""); if (string.IsNullOrWhiteSpace(reqOrg.Value)) { return(new Response(BaseErrorMessage(FhirConstants.JwtRequestingOrganization))); } if (!reqOrg.Value.StartsWith(FhirConstants.SystemOrgCode) || string.IsNullOrWhiteSpace(orgCode)) { return(new Response($"requesting_organisation ({reqOrg.Value}) must be of the form [{FhirConstants.SystemOrgCode}|[ODSCode]")); } if (!IsOrgInMap(orgCode)) { return(new Response($"The ODS code defined in the requesting_organisation({orgCode}) is unknown")); } // ### requesting_organization against requesting_system checks if (fromAsidMap.OrgCode != orgCode) { return(new Response($"requesting_system ASID ({fromAsid}) is not associated with the requesting_organisation ODS code ({orgCode})")); } return(new Response(true)); }