private CommandRequest BuildRequest(string asid, string resourceId, string nhsNumber, string custodianOrgCode, string typeCode, string jwtOrgCode, HttpMethod method, Resource resource) { var command = new CommandRequest { BaseUrl = $"{(_spineSettings.NrlsUseSecure ? _spineSettings.NrlsSecureServerUrl : _spineSettings.NrlsServerUrl)}", ResourceId = resourceId, ResourceType = ResourceType.DocumentReference, Resource = resource, SearchParams = GetParams(nhsNumber, custodianOrgCode, resourceId, typeCode), Method = method, //Content = content, UseSecure = _spineSettings.NrlsUseSecure, ClientThumbprint = _sdsService.GetFor(asid)?.Thumbprint, ServerThumbprint = _spineSettings.SpineThumbprint }; var jwt = JwtFactory.Generate(method == HttpMethod.Get ? JwtScopes.Read : JwtScopes.Write, jwtOrgCode, "fakeRoleId", asid, command.FullUrl.AbsoluteUri, SystemUrlBase, "DocumentReference"); command.Headers.Add(HeaderNames.Accept, ContentType.JSON_CONTENT_HEADER); command.Headers.Add(HeaderNames.Authorization, $"Bearer {jwt}"); command.Headers.Add(FhirConstants.HeaderFromAsid, asid); command.Headers.Add(FhirConstants.HeaderToAsid, _spineSettings.SpineAsid); return(command); }
private OperationOutcome ValidateOrganisationInteraction(string asid, string orgCode, bool isProviderCheck) { var providerInteractions = new string[] { FhirConstants.CreateInteractionId, FhirConstants.UpdateInteractionId, FhirConstants.DeleteInteractionId }; var cache = _sdsService.GetFor(asid); if (cache != null) { var valid = false; if (!string.IsNullOrEmpty(orgCode) && !string.IsNullOrEmpty(cache.OdsCode) && cache.OdsCode == orgCode) { valid = true; } if (isProviderCheck && (cache.Interactions == null || !cache.Interactions.Any(x => providerInteractions.Contains(x)))) { valid = false; } if (valid) { return(null); } } return(OperationOutcomeFactory.CreateAccessDenied()); }
private OperationOutcome InvalidAsid(string orgCode, string asid) { var cache = _sdsService.GetFor(asid); if (cache != null && !string.IsNullOrEmpty(orgCode) && !string.IsNullOrEmpty(cache.OdsCode) && cache.OdsCode == orgCode) { return(null); } return(OperationOutcomeFactory.CreateInvalidResource(FhirConstants.HeaderFromAsid, "The Custodian ODS code is not affiliated with the sender ASID.")); }
public async SystemTasks.Task Invoke(HttpContext context, IOptionsSnapshot <ApiSetting> nrlsApiSettings) { _nrlsApiSettings = nrlsApiSettings.Get("NrlsApiSetting"); //Order of validation is Important var request = context.Request; var headers = request.Headers; var method = request.Method; //Accept is optional but must be valid if supplied //Check is delegated to FhirInputMiddleware var authorization = GetHeaderValue(headers, HeaderNames.Authorization); var scope = method == HttpMethods.Get ? JwtScopes.Read : JwtScopes.Write; var jwtResponse = _nrlsValidation.ValidJwt(new Tuple <JwtScopes, string>(scope, "DocumentReference"), authorization); if (string.IsNullOrEmpty(authorization) || !jwtResponse.Success) { SetJwtError(HeaderNames.Authorization, jwtResponse.Message); } var fromASID = GetHeaderValue(headers, FhirConstants.HeaderFromAsid); var clientCache = _sdsService.GetFor(fromASID); if (clientCache == null) { SetError(FhirConstants.HeaderFromAsid, null); } var toASID = GetHeaderValue(headers, FhirConstants.HeaderToAsid); if (string.IsNullOrEmpty(toASID) || toASID != _spineSettings.Asid) { SetError(FhirConstants.HeaderToAsid, null); } var interactionId = GetInteractionId(method, request.Path.Value); if (string.IsNullOrEmpty(interactionId) || !clientCache.Interactions.Contains(interactionId)) { throw new HttpFhirException("Client interaction request invalid", OperationOutcomeFactory.CreateAccessDenied(), HttpStatusCode.Forbidden); } //We've Passed! Continue to App... await _next.Invoke(context); return; }
private CommandRequest BuildRequest(string asid, string jwtOrgCode, string providerOds) { var consumer = _sdsService.GetFor(asid); var provider = _sdsService.GetFor(providerOds, FhirConstants.ReadBinaryInteractionId); if (consumer == null) { throw new HttpFhirException("Local system not registered with SDS.", OperationOutcomeFactory.CreateGenericError($"Unknown ASID {asid}"), HttpStatusCode.BadRequest); } if (provider == null) { throw new HttpFhirException("External system not registered with SDS.", OperationOutcomeFactory.CreateGenericError($"Unknown ODS code {providerOds}"), HttpStatusCode.BadRequest); } var command = new CommandRequest { BaseUrl = $"{(_spineSettings.SspUseSecure ? _spineSettings.SspSecureServerUrl : _spineSettings.SspServerUrl)}", ResourceType = ResourceType.Binary, Method = HttpMethod.Get, UseSecure = _spineSettings.SspUseSecure, ClientThumbprint = consumer?.Thumbprint, ServerThumbprint = _spineSettings.SspSslThumbprint, RegenerateUrl = false }; var jwt = JwtFactory.Generate(JwtScopes.Read, jwtOrgCode, "fakeRoleId", asid, command.FullUrl.AbsoluteUri, SystemUrlBase, "*"); command.Headers.Add(HeaderNames.Authorization, $"Bearer {jwt}"); command.Headers.Add(FhirConstants.HeaderSspFrom, consumer?.Asid); // GET consumer ASID command.Headers.Add(FhirConstants.HeaderSspTo, provider?.Asid); // GET provider asid command.Headers.Add(FhirConstants.HeaderSspInterationId, FhirConstants.ReadBinaryInteractionId); command.Headers.Add(FhirConstants.HeaderSspTraceId, Guid.NewGuid().ToString()); return(command); }
public async Task Invoke(HttpContext context, IOptionsSnapshot <ApiSetting> apiSettings) { _nrlsApiSetting = apiSettings.Get("NrlsApiSetting"); //Fake SSP Interaction/ASID datastore if (_nrlsApiSetting.Secure && context.Request.IsHttps) { //var clientAsidMap = _cache.Get<ClientAsidMap>(ClientAsidMap.Key); var clientCertificate = context.Connection.ClientCertificate; using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) { //Get ASID var fromAsid = GetHeaderValue(context.Request.Headers, FhirConstants.HeaderFromAsid); if (string.IsNullOrEmpty(fromAsid) || clientCertificate == null) { SetError(); } //Check Certificate store.Open(OpenFlags.ReadOnly); var clientCertificates = store.Certificates.Find(X509FindType.FindByThumbprint, clientCertificate.Thumbprint, false); if (clientCertificates.Count < 1) { SetError(); } //Check client ASID Thumbprint against Supplied Certificate Thumbprint var client = _sdsService.GetFor(fromAsid); if (client == null || (client.Thumbprint.ToLowerInvariant() != clientCertificate.Thumbprint.ToLowerInvariant())) { SetError(); } } } await _next.Invoke(context); return; }
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 async SystemTasks.Task Invoke(HttpContext context) { //Basic headers check (asid, auth, etc) var headers = context.Request.Headers; var method = context.Request.Method; // -> JWT var authorization = GetHeaderValue(headers, HeaderNames.Authorization); var jwtResponse = _nrlsValidation.ValidJwt(new Tuple <JwtScopes, string>(JwtScopes.Read, "*"), authorization); if (string.IsNullOrEmpty(authorization) || !jwtResponse.Success) { SetJwtError(HeaderNames.Authorization, jwtResponse.Message); } // -> fromASID var fromASID = GetHeaderValue(headers, FhirConstants.HeaderSspFromAsid); var consumerCache = _sdsService.GetFor(fromASID); if (consumerCache == null) { SetError(FhirConstants.HeaderSspFromAsid, "The Ssp-From ASID header value is not known."); } // -> toASID var toASID = GetHeaderValue(headers, FhirConstants.HeaderSspToAsid); var providerCache = _sdsService.GetFor(toASID); if (providerCache == null) { SetError(FhirConstants.HeaderSspToAsid, "The Ssp-To ASID header value is not known."); } // -> traceID var traceId = GetHeaderValue(headers, FhirConstants.HeaderSspTradeId); if (string.IsNullOrEmpty(traceId)) { SetError(FhirConstants.HeaderSspTradeId, null); } // -> interactionID var interactionId = GetHeaderValue(headers, FhirConstants.HeaderSspInterationId); if (string.IsNullOrEmpty(interactionId) || !consumerCache.Interactions.Contains(interactionId)) { SetError(FhirConstants.HeaderSspInterationId, null); } //Provider FQDN check var providerOdsCache = _sdsService.GetFor(providerCache.OdsCode, interactionId); var forwardingUrl = WebUtility.UrlDecode(context.Request.Path.Value.Replace("/nrls-ri/SSP/", "")); var validFqdn = false; if (providerOdsCache == null) { SetError(FhirConstants.HeaderSspToAsid, "The Ssp-To ASID header value is not associated with the requested interaction."); } foreach (var endpoint in providerCache.EndPoints) { if (forwardingUrl.StartsWith(endpoint.AbsoluteUri)) { validFqdn = true; break; } } if (!validFqdn) { SetError(FhirConstants.HeaderSspToAsid, $"The FQDN in the request ({forwardingUrl}) does not match an FQDN registered for the provided {FhirConstants.HeaderSspToAsid} header."); } //TODO: ssl check await _next(context); }
public Response IsValid(string jwt, Tuple <JwtScopes, string> 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")); } //Skip header parsing //var header = claimsHashItems.First(); //Skip sig check for now as no service available to validate. //Current guidance is not to hash the sign for self generated JWTs. //var signature = claimsHashItems.Last(); //if (!string.IsNullOrEmpty(signature)) //{ // return new Response("The JWT associated with the Authorisation header must have an empty signature."); //} var claimsHash = claimsHashItems.Skip(1).Take(1).FirstOrDefault(); if (string.IsNullOrEmpty(claimsHash)) { return(new Response("The JWT associated with the Authorisation header must have a body of claims.")); } 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 as a valid JWT."; 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 = _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/{reqScope.Item2}.{reqScope.Item1.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 {expScope}")); } // ### 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_organization ({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_organization ({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_organization ODS code ({orgCode})")); } return(new Response(true)); }