예제 #1
0
        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);
        }
예제 #6
0
        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));
        }