/// <summary> /// Authetnicate /// </summary> private IDisposable Authenticate(Hl7MessageReceivedEventArgs e) { IPrincipal principal = null; var msh = e.Message.GetStructure("MSH") as MSH; var sft = e.Message.GetStructure("SFT") as SFT; var sessionService = ApplicationServiceContext.Current.GetService <ISessionProviderService>(); if (string.IsNullOrEmpty(msh.Security.Value) && this.m_configuration.Security == Configuration.AuthenticationMethod.Msh8) { this.m_traceSource.TraceError("Must carry MSH-8 authorization token information"); throw new SecurityException(this.m_localizationService.GetString("error.messaging.h17.authorizationToken")); } if (msh.Security.Value?.StartsWith("sid://") == true) // Session identifier { var session = sessionService.Get(Enumerable.Range(5, msh.Security.Value.Length - 5) .Where(x => x % 2 == 0) .Select(x => Convert.ToByte(msh.Security.Value.Substring(x, 2), 16)) .ToArray()); principal = ApplicationServiceContext.Current.GetService <ISessionIdentityProviderService>().Authenticate(session) as IClaimsPrincipal; } else if (e is AuthenticatedHl7MessageReceivedEventArgs auth && auth.AuthorizationToken != null) { // Ensure proper authentication exists if (String.IsNullOrEmpty(msh.SendingApplication.NamespaceID.Value)) { this.m_traceSource.TraceError("MSH-3 must be provided for authenticating device/application"); throw new SecurityException(this.m_localizationService.FormatString("error.messaging.h17.authenticating", new { param = "MSH-3", param2 = " device/application" })); } else if (this.m_configuration.Security == Configuration.AuthenticationMethod.Sft4 && string.IsNullOrEmpty(sft.SoftwareBinaryID.Value)) { this.m_traceSource.TraceError("SFT-4 must be provided for authenticating application"); throw new SecurityException(this.m_localizationService.FormatString("error.messaging.h17.authenticating", new { param = "SFT-4", param2 = " application" })); } else if (this.m_configuration.Security == Configuration.AuthenticationMethod.Msh8 && string.IsNullOrEmpty(msh.Security.Value)) { this.m_traceSource.TraceError("MSH-8 must be provided for authenticating application"); throw new SecurityException(this.m_localizationService.FormatString("error.messaging.h17.authenticating", new { param = "MSH-8", param2 = " application" })); } String applicationId = msh.SendingApplication.NamespaceID.Value, applicationSecret = null; switch (this.m_configuration.Security) { case Configuration.AuthenticationMethod.None: // No special - authenticate the app using device creds applicationSecret = this.m_configuration.NoAuthenticationSecret; break; case Configuration.AuthenticationMethod.Msh8: applicationSecret = msh.Security.Value; break; case Configuration.AuthenticationMethod.Sft4: applicationSecret = sft.SoftwareBinaryID.Value; break; } IPrincipal certificatePrincipal = ApplicationServiceContext.Current.GetService <ICertificateIdentityProvider>()?.Authenticate(auth.AuthorizationToken); if (certificatePrincipal == null) { throw new InvalidOperationException("In order to use node authentication with X509 certificates - there must be a CertificateIdentityProvider configured"); } else if (certificatePrincipal.Identity is IApplicationIdentity) { principal = certificatePrincipal; } else { var applicationPrincipal = applicationSecret != null?ApplicationServiceContext.Current.GetService <IApplicationIdentityProviderService>()?.Authenticate(applicationId, applicationSecret) : null; if (applicationPrincipal == null && this.m_configuration.RequireAuthenticatedApplication) { this.m_traceSource.TraceError("Server requires authenticated application"); throw new UnauthorizedAccessException(this.m_localizationService.GetString("error.type.UnauthorizedAccessException")); } principal = new SanteDBClaimsPrincipal(new IIdentity[] { certificatePrincipal.Identity, applicationPrincipal?.Identity }.OfType <IClaimsIdentity>()); } }
/// <summary> /// Apply the policy to the request /// </summary> public void Apply(RestRequestMessage request) { IDisposable context = null; try { this.m_traceSource.TraceInfo("Entering BasicAuthorizationAccessPolicy"); // Role service var roleService = ApplicationServiceContext.Current.GetService <IRoleProviderService>(); var identityService = ApplicationServiceContext.Current.GetService <IIdentityProviderService>(); var pipService = ApplicationServiceContext.Current.GetService <IPolicyInformationService>(); var pdpService = ApplicationServiceContext.Current.GetService <IPolicyDecisionService>(); var httpRequest = RestOperationContext.Current.IncomingRequest; var authHeader = httpRequest.Headers["Authorization"]; if (String.IsNullOrEmpty(authHeader) || !authHeader.ToLowerInvariant().StartsWith("basic")) { throw new AuthenticationException("Invalid authentication scheme"); } authHeader = authHeader.Substring(6); var b64Data = Encoding.UTF8.GetString(Convert.FromBase64String(authHeader)).Split(':'); if (b64Data.Length != 2) { throw new SecurityException("Malformed HTTP Basic Header"); } var principal = identityService.Authenticate(b64Data[0], b64Data[1]); if (principal == null) { throw new AuthenticationException("Invalid username/password"); } // Add claims made by the client var claims = new List <IClaim>(); if (principal is IClaimsPrincipal) { claims.AddRange((principal as IClaimsPrincipal).Claims); } var clientClaims = SanteDBClaimsUtil.ExtractClaims(httpRequest.Headers); foreach (var claim in clientClaims) { if (this.m_configuration?.AllowedClientClaims?.Contains(claim.Type) == false) { throw new SecurityException("Claim not allowed"); } else { var handler = SanteDBClaimsUtil.GetHandler(claim.Type); if (handler == null || handler.Validate(principal, claim.Value)) { claims.Add(claim); } else { throw new SecurityException("Claim validation failed"); } } } // Claim headers built in if (pipService != null) { claims.AddRange(pdpService.GetEffectivePolicySet(principal).Where(o => o.Rule == PolicyGrantType.Grant).Select(o => new SanteDBClaim(SanteDBClaimTypes.SanteDBGrantedPolicyClaim, o.Policy.Oid))); } // Finally validate the client var claimsPrincipal = new SanteDBClaimsPrincipal(new SanteDBClaimsIdentity(principal.Identity, claims)); if (this.m_configuration?.RequireClientAuth == true) { var clientAuth = httpRequest.Headers[SanteDBRestConstants.BasicHttpClientCredentialHeaderName]; if (clientAuth == null || !clientAuth.StartsWith("basic", StringComparison.InvariantCultureIgnoreCase)) { throw new SecurityException("Client credentials invalid"); } else { String clientAuthString = clientAuth.Substring(clientAuth.IndexOf("basic", StringComparison.InvariantCultureIgnoreCase) + 5).Trim(); String[] authComps = Encoding.UTF8.GetString(Convert.FromBase64String(clientAuthString)).Split(':'); var applicationPrincipal = ApplicationServiceContext.Current.GetService <IApplicationIdentityProviderService>().Authenticate(authComps[0], authComps[1]); claimsPrincipal.AddIdentity(applicationPrincipal.Identity as IClaimsIdentity); } } context = AuthenticationContext.EnterContext(principal); } catch (Exception e) { this.m_traceSource.TraceEvent(EventLevel.Error, e.ToString()); } finally { // Disposed context so reset the auth RestOperationContext.Current.Disposed += (o, e) => context?.Dispose(); } }
/// <summary> /// Create an authorization context /// </summary> public IClaimsPrincipal CreateClaimsPrincipal(IEnumerable <IClaimsIdentity> otherIdentities = null) { if (!this.IsAuthenticated) { throw new SecurityException("Principal is not authenticated"); } try { // System claims List <IClaim> claims = new List <IClaim>( ) { new SanteDBClaim(SanteDBClaimTypes.AuthenticationMethod, this.m_authenticationType ?? "LOCAL"), new SanteDBClaim(SanteDBClaimTypes.Sid, this.m_securityUser.Key.ToString()), new SanteDBClaim(SanteDBClaimTypes.NameIdentifier, this.m_securityUser.Key.ToString()), new SanteDBClaim(SanteDBClaimTypes.Actor, this.m_securityUser.UserClass.ToString()), }; if (!this.Claims.Any(o => o.Type == SanteDBClaimTypes.Name)) { claims.Add(new SanteDBClaim(SanteDBClaimTypes.Name, this.m_securityUser.UserName)); } if (!this.Claims.Any(o => o.Type == SanteDBClaimTypes.DefaultRoleClaimType)) { claims.AddRange(this.m_roles.Select(r => new SanteDBClaim(SanteDBClaimTypes.DefaultRoleClaimType, r.Name))); } if (this.m_securityUser.PasswordExpiration.HasValue && this.m_securityUser.PasswordExpiration < DateTime.Now) { claims.Add(new SanteDBClaim(SanteDBClaimTypes.PurposeOfUse, PurposeOfUseKeys.SecurityAdmin.ToString())); claims.Add(new SanteDBClaim(SanteDBClaimTypes.SanteDBScopeClaim, PermissionPolicyIdentifiers.LoginPasswordOnly)); claims.Add(new SanteDBClaim(SanteDBClaimTypes.SanteDBScopeClaim, PermissionPolicyIdentifiers.ReadMetadata)); } if (this.m_securityUser.Email != null) { claims.Add(new SanteDBClaim(SanteDBClaimTypes.Email, this.m_securityUser.Email)); } if (this.m_securityUser.PhoneNumber != null) { claims.Add(new SanteDBClaim(SanteDBClaimTypes.Telephone, this.m_securityUser.PhoneNumber)); } this.AddClaims(claims); var identities = new IClaimsIdentity[] { this }; if (otherIdentities != null) { identities = identities.Union(otherIdentities).ToArray(); } // TODO: Demographic data for the user var retVal = new SanteDBClaimsPrincipal( identities ); s_traceSource.TraceInfo("Created security principal from identity {0} > {1}", this, AdoClaimsIdentity.PrincipalToString(retVal)); return(retVal); } catch (Exception e) { s_traceSource.TraceEvent(EventLevel.Error, e.ToString()); throw new Exception("Creating principal from identity failed", e); } }
/// <summary> /// Authetnicate /// </summary> private void Authenticate(Hl7MessageReceivedEventArgs e) { IPrincipal principal = null; var msh = e.Message.GetStructure("MSH") as MSH; var sft = e.Message.GetStructure("SFT") as SFT; var sessionService = ApplicationServiceContext.Current.GetService <ISessionProviderService>(); if (String.IsNullOrEmpty(msh.Security.Value) && this.m_configuration.Security == SecurityMethod.Msh8) { throw new SecurityException("Must carry MSH-8 authorization token information"); } if (msh.Security.Value?.StartsWith("sid://") == true) // Session identifier { var session = sessionService.Get(Enumerable.Range(5, msh.Security.Value.Length - 5) .Where(x => x % 2 == 0) .Select(x => Convert.ToByte(msh.Security.Value.Substring(x, 2), 16)) .ToArray()); principal = ApplicationServiceContext.Current.GetService <ISessionIdentityProviderService>().Authenticate(session) as IClaimsPrincipal; } else if (e is AuthenticatedHl7MessageReceivedEventArgs) { var auth = e as AuthenticatedHl7MessageReceivedEventArgs; // Ensure proper authentication exists if (String.IsNullOrEmpty(msh.SendingFacility.NamespaceID.Value)) { throw new SecurityException("MSH-4 must be provided for authenticating device"); } else if (String.IsNullOrEmpty(msh.SendingApplication.NamespaceID.Value)) { throw new SecurityException("MSH-3 must be provided for authenticating device/application"); } else if (this.m_configuration.Security == SecurityMethod.Sft4 && String.IsNullOrEmpty(sft.SoftwareBinaryID.Value)) { throw new SecurityException("SFT-4 must be provided for authenticating application"); } else if (this.m_configuration.Security == SecurityMethod.Msh8 && String.IsNullOrEmpty(msh.Security.Value)) { throw new SecurityException("MSH-8 must be provided for authenticating application"); } String deviceId = $"{msh.SendingApplication.NamespaceID.Value}|{msh.SendingFacility.NamespaceID.Value}", deviceSecret = BitConverter.ToString(auth.AuthorizationToken).Replace("-", ""), applicationId = msh.SendingApplication.NamespaceID.Value, applicationSecret = this.m_configuration.Security == SecurityMethod.Sft4 ? sft.SoftwareBinaryID.Value : // Authenticate app by SFT4 this.m_configuration.Security == SecurityMethod.Msh8 ? msh.Security.Value : // Authenticate app by MSH-8 BitConverter.ToString(auth.AuthorizationToken).Replace("-", ""); // Authenticate app using X509 certificate on the device IPrincipal devicePrincipal = ApplicationServiceContext.Current.GetService <IDeviceIdentityProviderService>().Authenticate(deviceId, deviceSecret, AuthenticationMethod.Local), applicationPrincipal = applicationSecret != null?ApplicationServiceContext.Current.GetService <IApplicationIdentityProviderService>()?.Authenticate(applicationId, applicationSecret) : null; if (applicationPrincipal == null && ApplicationServiceContext.Current.HostType == SanteDBHostType.Server) { throw new UnauthorizedAccessException("Server requires authenticated application"); } principal = new SanteDBClaimsPrincipal(new IIdentity[] { devicePrincipal.Identity, applicationPrincipal?.Identity }.OfType <IClaimsIdentity>()); } else if (this.m_configuration.Security != SecurityMethod.None) { // Ensure proper authentication exists if (String.IsNullOrEmpty(msh.SendingFacility.NamespaceID.Value) || String.IsNullOrEmpty(msh.Security.Value)) { throw new SecurityException("MSH-4 and MSH-8 must always be provided for authenticating device when SLLP is not used"); } else if (String.IsNullOrEmpty(msh.SendingFacility.NamespaceID.Value)) { throw new SecurityException("MSH-3 must be provided for authenticating application"); } else if (this.m_configuration.Security == SecurityMethod.Sft4 && String.IsNullOrEmpty(sft.SoftwareBinaryID.Value)) { throw new SecurityException("SFT-4 must be provided for authenticating application"); } else if (this.m_configuration.Security == SecurityMethod.Msh8 && String.IsNullOrEmpty(msh.Security.Value)) { throw new SecurityException("MSH-8 must be provided for authenticating application"); } String deviceId = $"{msh.SendingApplication.NamespaceID.Value}|{msh.SendingFacility.NamespaceID.Value}", deviceSecret = msh.Security.Value, applicationId = msh.SendingApplication.NamespaceID.Value, applicationSecret = this.m_configuration.Security == SecurityMethod.Sft4 ? sft.SoftwareBinaryID.Value : this.m_configuration.Security == SecurityMethod.Msh8 ? msh.Security.Value : null; if (applicationSecret == deviceSecret && applicationSecret.Contains("+")) // Both device and app are using same auth key? Odd, perhaps there is the delimeter { var secrets = applicationSecret.Split('+'); applicationSecret = secrets[1]; deviceSecret = secrets[0]; } IPrincipal devicePrincipal = ApplicationServiceContext.Current.GetService <IDeviceIdentityProviderService>().Authenticate(deviceId, deviceSecret, AuthenticationMethod.Local), applicationPrincipal = applicationSecret != null?ApplicationServiceContext.Current.GetService <IApplicationIdentityProviderService>()?.Authenticate(applicationId, applicationSecret) : null; if (applicationPrincipal == null && ApplicationServiceContext.Current.HostType == SanteDBHostType.Server) { throw new UnauthorizedAccessException("Server requires authenticated application"); } principal = new SanteDBClaimsPrincipal(new IIdentity[] { devicePrincipal.Identity, applicationPrincipal?.Identity }.OfType <IClaimsIdentity>()); } else { switch (this.m_configuration.AnonymousUser?.ToUpper()) { case "SYSTEM": principal = AuthenticationContext.SystemPrincipal; break; case "ANONYMOUS": default: principal = AuthenticationContext.AnonymousPrincipal; break; } } // Pricipal if (principal != null) { AuthenticationContext.Current = new AuthenticationContext(principal); } }