예제 #1
0
        public void ProcessOidcAuthorizationResponse_FailedTokenRequest(string response, HttpStatusCode httpStatusCode, Type exceptionType, string message)
        {
            var context = RequestContext.GetContext();

            OidcIdentityProvider     idProvider;
            OidcIdentityProviderUser idProviderUser;
            UserAccount userAccount;
            string      tenantName = GetCurrentTenantName();

            CreateEntityModel("*****@*****.**", true, out idProvider, out idProviderUser, out userAccount);

            var code = Guid.NewGuid().ToString();

            // Mock config provider and http client

            var errorResponseMessage = new HttpResponseMessage {
                Content = new StringContent(response), StatusCode = httpStatusCode
            };

            var mockRepo      = new MockRepository(MockBehavior.Strict);
            var configUrl     = idProvider.OidcIdentityProviderConfigurationUrl;
            var configManager = mockRepo.Create <IOpenIdConnectConfigurationManager>();

            configManager.Setup(w => w.GetIdentityProviderConfigurationAsync(configUrl)).Returns(Task.FromResult(_oidcConfig));
            configManager.Setup(w => w.RemoveIdentityProviderConfiguration(configUrl));
            var httpClient = mockRepo.Create <IHttpClient>();

            httpClient.Setup(w => w.PostAsync(new Uri(_oidcConfig.TokenEndpoint), It.IsAny <HttpContent>()))
            .Returns(Task.FromResult(errorResponseMessage));

            var oidcLoginHandler = new OpenIdConnectLoginHandler(configManager.Object);

            var idpLoginRequest = new IdentityProviderLoginRequest
            {
                Tenant             = context.Tenant.Name,
                IdentityProviderId = idProvider.Id,
                RedirectUrl        = "https://test.com/login/callback"
            };
            var baseUri = new Uri("https://test.com");

            // Get code auth uri, get state
            var uri = Task.Run(() => oidcLoginHandler.GetAuthorizationCodeRequestUrl(idpLoginRequest, baseUri)).Result;

            var queryValues    = uri.ParseQueryString();
            var state          = queryValues["state"];
            var validatedState = oidcLoginHandler.ValidateAuthState(state);

            Assert.That(
                async() =>
                await
                oidcLoginHandler.ProcessOidcAuthorizationResponse(tenantName, code, validatedState, baseUri, httpClient.Object),
                Throws.TypeOf(exceptionType).And.Message.EqualTo(message));

            mockRepo.VerifyAll();
        }
        /// <summary>
        ///     Gets the authorization code request URL for the specified identity provider.
        /// </summary>
        /// <param name="idpLoginRequest">The oidc provider.</param>
        /// <param name="requestBaseUrl">The base url message.</param>
        /// <returns>Task&lt;Uri&gt;.</returns>
        /// <exception cref="System.ArgumentNullException">
        /// </exception>
        public async Task <Uri> GetAuthorizationCodeRequestUrl(IdentityProviderLoginRequest idpLoginRequest, Uri requestBaseUrl)
        {
            if (idpLoginRequest == null)
            {
                throw new ArgumentNullException(nameof(idpLoginRequest));
            }

            if (requestBaseUrl == null)
            {
                throw new ArgumentNullException(nameof(requestBaseUrl));
            }

            if (string.IsNullOrWhiteSpace(idpLoginRequest.Tenant))
            {
                throw new ArgumentException(@"The tenant is invalid.", nameof(idpLoginRequest));
            }

            if (idpLoginRequest.IdentityProviderId <= 0)
            {
                throw new ArgumentException(@"The identity provider is invalid.", nameof(idpLoginRequest));
            }

            if (string.IsNullOrWhiteSpace(idpLoginRequest.RedirectUrl))
            {
                throw new ArgumentException(@"The redirect url is invalid.", nameof(idpLoginRequest));
            }

            long   tenantId;
            long   oidcProviderId;
            string oidcConfigurationUrl;
            string oidcClientId;
            bool   alwaysPrompt;

            using (new SecurityBypassContext())
                using (new TenantAdministratorContext(idpLoginRequest.Tenant))
                {
                    var oidcIdentityProvider = ReadiNow.Model.Entity.Get <OidcIdentityProvider>(idpLoginRequest.IdentityProviderId, GetOidcProviderFieldsToLoad());

                    if (oidcIdentityProvider == null)
                    {
                        throw new AuthenticationException("The identity provider does not exist.");
                    }

                    ValidateOidcProviderFields(oidcIdentityProvider);

                    // Store any required entity model fields upfront.
                    // Any code running after the await statement may run a different thread
                    tenantId             = RequestContext.TenantId;
                    oidcProviderId       = oidcIdentityProvider.Id;
                    oidcClientId         = oidcIdentityProvider.OidcClientId;
                    oidcConfigurationUrl = oidcIdentityProvider.OidcIdentityProviderConfigurationUrl;
                    alwaysPrompt         = oidcIdentityProvider.OidcAlwaysPrompt ?? true;
                }

            OpenIdConnectConfiguration oidcConfig;

            try
            {
                // Get the configuration
                oidcConfig = await _configurationManager.GetIdentityProviderConfigurationAsync(oidcConfigurationUrl);
            }
            catch (Exception ex)
            {
                throw new OidcProviderInvalidConfigurationException(ex);
            }

            var oidcProtocolValidator = new OpenIdConnectProtocolValidator();

            // Create authorization state
            var authStateObject = new OpenIdConnectAuthorizationState
            {
                Timestamp          = DateTime.UtcNow.Ticks,
                RedirectUrl        = idpLoginRequest.RedirectUrl,
                IdentityProviderId = oidcProviderId,
                TenantId           = tenantId,
                Nonce = oidcProtocolValidator.GenerateNonce()
            };

            // Serialize and encrypt state
            var stateJson      = JSON.Serialize(authStateObject);
            var cryptoProvider = new EncodingCryptoProvider();
            var encryptedState = cryptoProvider.EncryptAndEncode(stateJson);

            // Create code request oidc message
            var oidcMessage = new OpenIdConnectMessage
            {
                ClientId      = oidcClientId,
                IssuerAddress = oidcConfig.AuthorizationEndpoint,
                RedirectUri   = GetOidcAuthResponseUri(requestBaseUrl, idpLoginRequest.Tenant),
                Scope         = "openid email",
                ResponseType  = "code",
                State         = encryptedState,
                Nonce         = authStateObject.Nonce,
                Prompt        = alwaysPrompt ? "login" : null
            };

            // Get request url
            return(new Uri(oidcMessage.CreateAuthenticationRequestUrl()));
        }
예제 #3
0
        public void ProcessOidcAuthorizationResponse_InvalidUser(string test)
        {
            var context = RequestContext.GetContext();

            OidcIdentityProvider     idProvider = null;
            OidcIdentityProviderUser idProviderUser;
            UserAccount userAccount;
            string      tenantName = GetCurrentTenantName();

            switch (test)
            {
            case "NoIdpUser":
                // Create user account with a different name
                CreateEntityModel("testUser", true, out idProvider, out idProviderUser, out userAccount);
                break;

            case "NoUserAccount":
                // Create idp user with matching name but without associated account
                CreateEntityModelNoUserAccount("*****@*****.**", out idProvider, out idProviderUser);
                break;

            case "DisabledUserAccount":
                // Create user account with a disabled account
                CreateEntityModel("*****@*****.**", false, out idProvider, out idProviderUser, out userAccount);
                break;
            }

            var code = Guid.NewGuid().ToString();

            // Mock config provider and http client
            var expectedTokenRequestMessage = new OpenIdConnectMessage
            {
                GrantType    = "authorization_code",
                Code         = code,
                ClientSecret = Factory.SecuredData.Read((Guid)idProvider.OidcClientSecretSecureId),
                ClientId     = idProvider.OidcClientId,
                RedirectUri  = "https://test.com/spapi/data/v1/login/oidc/authresponse/" + tenantName
            };

            var validResponseMessage = new HttpResponseMessage {
                Content = new StringContent(_openIdTokenResponse)
            };

            var mockRepo      = new MockRepository(MockBehavior.Strict);
            var configUrl     = idProvider.OidcIdentityProviderConfigurationUrl;
            var configManager = mockRepo.Create <IOpenIdConnectConfigurationManager>();

            configManager.Setup(w => w.GetIdentityProviderConfigurationAsync(configUrl)).Returns(Task.FromResult(_oidcConfig));
            var httpClient = mockRepo.Create <IHttpClient>();

            httpClient.Setup(w => w.PostAsync(new Uri(_oidcConfig.TokenEndpoint), It.Is <HttpContent>(c => ValidateTokenRequestMessage(expectedTokenRequestMessage, c))))
            .Returns(Task.FromResult(validResponseMessage));

            var oidcLoginHandler = new OpenIdConnectLoginHandler(configManager.Object, true);

            var idpLoginRequest = new IdentityProviderLoginRequest
            {
                Tenant             = context.Tenant.Name,
                IdentityProviderId = idProvider.Id,
                RedirectUrl        = "https://test.com/login/callback/"
            };
            var baseUri = new Uri("https://test.com");

            // Get code auth uri, get state
            var uri = Task.Run(() => oidcLoginHandler.GetAuthorizationCodeRequestUrl(idpLoginRequest, baseUri)).Result;

            var queryValues    = uri.ParseQueryString();
            var state          = queryValues["state"];
            var validatedState = oidcLoginHandler.ValidateAuthState(state);

            Assert.That(
                async() =>
                await oidcLoginHandler.ProcessOidcAuthorizationResponse(tenantName, code, validatedState, baseUri, httpClient.Object),
                Throws.TypeOf <AuthenticationException>().And.Message.EqualTo("The request context could not be found. The user name may be incorrect or the account may be locked, disabled or expired."));

            mockRepo.VerifyAll();
        }
예제 #4
0
        public void ProcessOidcAuthorizationResponse_EnabledTokenValidation()
        {
            var context = RequestContext.GetContext();

            OidcIdentityProvider     idProvider;
            OidcIdentityProviderUser idProviderUser;
            UserAccount userAccount;
            string      tenantName = GetCurrentTenantName();

            CreateEntityModel("*****@*****.**", true, out idProvider, out idProviderUser, out userAccount);

            var code = Guid.NewGuid().ToString();

            // Mock config provider and http client
            var expectedTokenRequestMessage = new OpenIdConnectMessage
            {
                GrantType    = "authorization_code",
                Code         = code,
                ClientSecret = Factory.SecuredData.Read((Guid)idProvider.OidcClientSecretSecureId),
                ClientId     = idProvider.OidcClientId,
                RedirectUri  = "https://test.com/spapi/data/v1/login/oidc/authresponse/" + tenantName
            };

            var validResponseMessage = new HttpResponseMessage {
                Content = new StringContent(_openIdTokenResponse)
            };

            var mockRepo      = new MockRepository(MockBehavior.Strict);
            var configUrl     = idProvider.OidcIdentityProviderConfigurationUrl;
            var configManager = mockRepo.Create <IOpenIdConnectConfigurationManager>();

            configManager.Setup(w => w.GetIdentityProviderConfigurationAsync(configUrl)).Returns(Task.FromResult(_oidcConfig));
            configManager.Setup(w => w.RemoveIdentityProviderConfiguration(configUrl));
            var httpClient = mockRepo.Create <IHttpClient>();

            httpClient.Setup(w => w.PostAsync(new Uri(_oidcConfig.TokenEndpoint), It.Is <HttpContent>(c => ValidateTokenRequestMessage(expectedTokenRequestMessage, c))))
            .Returns(Task.FromResult(validResponseMessage));

            var oidcLoginHandler = new OpenIdConnectLoginHandler(configManager.Object);

            var idpLoginRequest = new IdentityProviderLoginRequest
            {
                Tenant             = context.Tenant.Name,
                IdentityProviderId = idProvider.Id,
                RedirectUrl        = "https://test.com/login/callback"
            };
            var baseUri = new Uri("https://test.com");

            // Get code auth uri, get state
            var uri = Task.Run(() => oidcLoginHandler.GetAuthorizationCodeRequestUrl(idpLoginRequest, baseUri)).Result;

            var queryValues    = uri.ParseQueryString();
            var state          = queryValues["state"];
            var validatedState = oidcLoginHandler.ValidateAuthState(state);

            Assert.That(
                async() =>
                await
                oidcLoginHandler.ProcessOidcAuthorizationResponse(tenantName, code, validatedState, baseUri, httpClient.Object),
                Throws.TypeOf <AuthenticationException>());
        }
예제 #5
0
        public void GetAuthorizationCodeRequestUrl()
        {
            var context = RequestContext.GetContext();

            OidcIdentityProvider     idProvider;
            OidcIdentityProviderUser idProviderUser;
            UserAccount userAccount;

            // Setup entity model
            CreateEntityModel("IDP User" + Guid.NewGuid(), false, out idProvider, out idProviderUser, out userAccount);

            // Create mock
            var mockRepo      = new MockRepository(MockBehavior.Strict);
            var configUrl     = idProvider.OidcIdentityProviderConfigurationUrl;
            var configManager = mockRepo.Create <IOpenIdConnectConfigurationManager>();

            configManager.Setup(w => w.GetIdentityProviderConfigurationAsync(configUrl)).Returns(Task.FromResult(_oidcConfig));

            var idpLoginRequest = new IdentityProviderLoginRequest
            {
                Tenant             = context.Tenant.Name,
                IdentityProviderId = idProvider.Id,
                RedirectUrl        = "https://test.com/login/callback"
            };
            var baseUri = new Uri("https://test.com");

            var oidcLoginHandler = new OpenIdConnectLoginHandler(configManager.Object);

            var timeStampBefore = DateTime.UtcNow;

            var uri = Task.Run(() => oidcLoginHandler.GetAuthorizationCodeRequestUrl(idpLoginRequest, baseUri)).Result;

            var timeStampAfter = DateTime.UtcNow;

            // Validate url
            Assert.AreEqual("rndev20adfs.sp.local", uri.Host);
            Assert.AreEqual("/adfs/oauth2/authorize/", uri.AbsolutePath);

            var queryValues = uri.ParseQueryString();

            Assert.AreEqual(idProvider.OidcClientId, queryValues["client_id"], "The client_id is invalid");
            Assert.AreEqual("https://test.com/spapi/data/v1/login/oidc/authresponse/" + context.Tenant.Name.ToLowerInvariant(), queryValues["redirect_uri"], "The redirect_uri is invalid");
            Assert.AreEqual("openid email", queryValues["scope"], "The scope is invalid");
            Assert.AreEqual("code", queryValues["response_type"], "The response_type is invalid");
            Assert.AreEqual("login", queryValues["prompt"], "The prompt is invalid");

            var nonce = queryValues["nonce"];

            Assert.IsNotNullOrEmpty(nonce, "The nonce is invalid.");

            var state = queryValues["state"];

            Assert.IsNotNullOrEmpty(state, "The state is invalid.");

            var validatedState = oidcLoginHandler.ValidateAuthState(state);

            Assert.AreEqual(context.Tenant.Id, validatedState.TenantId, "The state TenantId is invalid");
            Assert.AreEqual(idProvider.Id, validatedState.IdentityProviderId, "The state IdentityProviderId is invalid");
            Assert.AreEqual(nonce, validatedState.Nonce, "The state Nonce is invalid");
            Assert.AreEqual(idpLoginRequest.RedirectUrl, validatedState.RedirectUrl, "The state RedirectUrl is invalid");

            var timeStamp = new DateTime(validatedState.Timestamp, DateTimeKind.Utc);

            // Verify timestamp is between times
            Assert.GreaterOrEqual(timeStamp, timeStampBefore);
            Assert.LessOrEqual(timeStamp, timeStampAfter);

            mockRepo.VerifyAll();
        }
예제 #6
0
 /// <inheritdoc/>
 public ClientResponse <LoginResponse> ReconcileJWT(IdentityProviderLoginRequest request)
 {
     return(client.ReconcileJWTAsync(request).GetAwaiter().GetResult());
 }
예제 #7
0
 /// <inheritdoc/>
 public ClientResponse <LoginResponse> IdentityProviderLogin(IdentityProviderLoginRequest request)
 {
     return(client.IdentityProviderLoginAsync(request).GetAwaiter().GetResult());
 }