Ejemplo n.º 1
0
        public void ValidateAuthState_NullEmptyState()
        {
            var mockRepo         = new MockRepository(MockBehavior.Loose);
            var configManager    = mockRepo.Create <IOpenIdConnectConfigurationManager>();
            var oidcLoginHandler = new OpenIdConnectLoginHandler(configManager.Object);

            Assert.That(
                () =>
                oidcLoginHandler.ValidateAuthState(null),
                Throws.TypeOf <ArgumentNullException>().And.Property("ParamName").EqualTo("state"));

            Assert.That(
                () =>
                oidcLoginHandler.ValidateAuthState(string.Empty),
                Throws.TypeOf <ArgumentNullException>().And.Property("ParamName").EqualTo("state"));
        }
Ejemplo n.º 2
0
        public void ValidateAuthState_InvalidState(long timestamp, string redirectUrl, long identityProviderId, long tenantId, string nonce)
        {
            var mockRepo         = new MockRepository(MockBehavior.Loose);
            var configManager    = mockRepo.Create <IOpenIdConnectConfigurationManager>();
            var oidcLoginHandler = new OpenIdConnectLoginHandler(configManager.Object);

            // Create invalid auth state
            var authState = new OpenIdConnectAuthorizationState
            {
                Timestamp          = timestamp,
                RedirectUrl        = redirectUrl,
                IdentityProviderId = identityProviderId,
                TenantId           = tenantId,
                Nonce = nonce
            };

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

            Assert.That(
                () =>
                oidcLoginHandler.ValidateAuthState(encryptedState),
                Throws.TypeOf <AuthenticationException>().And.Message.EqualTo("The authorization state has invalid data."));
        }
Ejemplo n.º 3
0
        public void ValidateAuthState_ExpiredState()
        {
            var mockRepo         = new MockRepository(MockBehavior.Loose);
            var configManager    = mockRepo.Create <IOpenIdConnectConfigurationManager>();
            var oidcLoginHandler = new OpenIdConnectLoginHandler(configManager.Object);

            // Create invalid auth state
            var authState = new OpenIdConnectAuthorizationState
            {
                Timestamp          = DateTime.UtcNow.AddHours(10).Ticks,
                RedirectUrl        = "http://test.com",
                IdentityProviderId = 100,
                TenantId           = 100,
                Nonce = "xx"
            };

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

            Assert.That(
                () =>
                oidcLoginHandler.ValidateAuthState(encryptedState1),
                Throws.TypeOf <AuthenticationException>().And.Message.EqualTo("The authorization state has expired."));

            // Create invalid auth state
            authState = new OpenIdConnectAuthorizationState
            {
                Timestamp          = DateTime.UtcNow.AddHours(-10).Ticks,
                RedirectUrl        = "http://test.com",
                IdentityProviderId = 100,
                TenantId           = 100,
                Nonce = "xx"
            };

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

            Assert.That(
                () =>
                oidcLoginHandler.ValidateAuthState(encryptedState2),
                Throws.TypeOf <AuthenticationException>().And.Message.EqualTo("The authorization state has expired."));
        }
Ejemplo n.º 4
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();
        }
Ejemplo n.º 5
0
        public void ValidateAuthState_CorruptState()
        {
            var mockRepo         = new MockRepository(MockBehavior.Loose);
            var configManager    = mockRepo.Create <IOpenIdConnectConfigurationManager>();
            var oidcLoginHandler = new OpenIdConnectLoginHandler(configManager.Object);

            Assert.That(
                () =>
                oidcLoginHandler.ValidateAuthState("xx"),
                Throws.TypeOf <AuthenticationException>().And.Message.EqualTo("The authorization state is invalid."));
        }
Ejemplo n.º 6
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();
        }
Ejemplo n.º 7
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>());
        }
Ejemplo n.º 8
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();
        }
Ejemplo n.º 9
0
        // ReSharper disable once InconsistentNaming
        public async Task <IHttpActionResult> OidcAuthResponseCallback(string tenant, string code = null, string state = null, string error = null, string error_description = null)
        {
            OpenIdConnectAuthorizationState authState = null;

            try
            {
                if (!string.IsNullOrWhiteSpace(error))
                {
                    return(BadRequest($"An error occurred during authorization. Error:{error}. Description:{error_description}"));
                }

                if (string.IsNullOrWhiteSpace(tenant))
                {
                    throw new WebArgumentNullException("tenant", "The tenant was not specified");
                }

                if (string.IsNullOrWhiteSpace(code))
                {
                    throw new WebArgumentNullException("code", "The code was not specified");
                }

                if (string.IsNullOrWhiteSpace(state))
                {
                    throw new WebArgumentNullException("state", "The state was not specified");
                }

                // For some reason ADFS is replacing the + signs (even the encoded ones) with spaces, so we undo it.
                // State is Base64 encoded so it should not contain spaces.
                if (!string.IsNullOrWhiteSpace(state))
                {
                    state = state.Replace(' ', '+');
                }

                var oidcLoginHandler = new OpenIdConnectLoginHandler(OpenIdConnectConfigurationManager);
                if (oidcLoginHandler.IsTokenValidationDisabled)
                {
                    EventLog.Application.WriteError("OpenIdConnectLoginHandler has token validation disabled. This is not be used in production.");
                    throw new AuthenticationException();
                }

                // Validate the state.
                authState = oidcLoginHandler.ValidateAuthState(state);

                using (var httpClient = new BasicHttpClient())
                {
                    // Process the authorization response.
                    var result = await oidcLoginHandler.ProcessOidcAuthorizationResponse(tenant, code, authState, new Uri(Request.RequestUri.GetLeftPart(UriPartial.Authority)), httpClient);

                    CookieHelper.CreateAuthenticationAndXsrfCookies(authState.TenantId, authState.IdentityProviderId, result.IdentityProviderUserName, result.RequestContextData.Identity.Id, true);
                }

                return(Redirect(authState.RedirectUrl));
            }
            catch (Exception exception)
            {
                var oidcConfigException = exception as OidcProviderInvalidConfigurationException;

                EventLog.Application.WriteError("Failed to process oidc authorization response. Error: {0}", exception.ToString());

                CookieHelper.DeleteAuthenticationAndXsrfCookies();
                if (!string.IsNullOrWhiteSpace(authState?.RedirectUrl))
                {
                    string delimiter = authState.RedirectUrl.Contains("?") ? "&" : "?";
                    string errorType = oidcConfigException != null ? "idpconfigerror" : "autherror";
                    return(Redirect(authState.RedirectUrl + delimiter + "error=" + errorType));
                }

                return(Unauthorized());
            }
        }