public void ProcessOidcAuthorizationResponse_InvalidAuthState() { var mockRepo = new MockRepository(MockBehavior.Loose); var configManager = mockRepo.Create <IOpenIdConnectConfigurationManager>(); var oidcLoginHandler = new OpenIdConnectLoginHandler(configManager.Object); string tenantName = GetCurrentTenantName(); var authState1 = new OpenIdConnectAuthorizationState { TenantId = RequestContext.TenantId, Timestamp = DateTime.UtcNow.Ticks, IdentityProviderId = 1000, Nonce = "xx", RedirectUrl = "http://test.com" }; Assert.That( async() => await oidcLoginHandler.ProcessOidcAuthorizationResponse(tenantName, "code", authState1, new Uri("http://test.com"), new BasicHttpClient()), Throws.TypeOf <AuthenticationException>().And.Message.EqualTo("The identity provider does not exist.")); var authState2 = new OpenIdConnectAuthorizationState { TenantId = 100, Timestamp = DateTime.UtcNow.Ticks, IdentityProviderId = 1000, Nonce = "xx", RedirectUrl = "http://test.com" }; Assert.That( async() => await oidcLoginHandler.ProcessOidcAuthorizationResponse(tenantName, "code", authState2, new Uri("http://test.com"), new BasicHttpClient()), Throws.TypeOf <AuthenticationException>().And.Message.EqualTo("The tenant is invalid.")); }
public void ProcessOidcAuthorizationResponse_InvalidProviderSetup(string clientId, string clientSecret, string configUrl, string claim, bool enabled) { var mockRepo = new MockRepository(MockBehavior.Loose); var configManager = mockRepo.Create <IOpenIdConnectConfigurationManager>(); var oidcLoginHandler = new OpenIdConnectLoginHandler(configManager.Object); string tenantName = GetCurrentTenantName(); var provider = new OidcIdentityProvider { Name = "ID Provider " + Guid.NewGuid(), IsProviderEnabled = enabled, OidcClientId = clientId, OidcClientSecret = clientSecret, OidcIdentityProviderConfigurationUrl = configUrl, OidcUserIdentityClaim = claim }; var authState = new OpenIdConnectAuthorizationState { TenantId = RequestContext.TenantId, IdentityProviderId = provider.Id }; Assert.That( async() => await oidcLoginHandler.ProcessOidcAuthorizationResponse(tenantName, "code", authState, new Uri("http://test.com"), new BasicHttpClient()), Throws.TypeOf <AuthenticationException>()); }
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(); }
public void ProcessOidcAuthorizationResponse_InvalidParams() { var mockRepo = new MockRepository(MockBehavior.Loose); var configManager = mockRepo.Create <IOpenIdConnectConfigurationManager>(); var oidcLoginHandler = new OpenIdConnectLoginHandler(configManager.Object); Assert.That( async() => await oidcLoginHandler.ProcessOidcAuthorizationResponse(null, null, new OpenIdConnectAuthorizationState(), new Uri("http://test.com"), new BasicHttpClient()), Throws.TypeOf <ArgumentNullException>().And.Property("ParamName").EqualTo("tenant")); Assert.That( async() => await oidcLoginHandler.ProcessOidcAuthorizationResponse("EDC", null, new OpenIdConnectAuthorizationState(), new Uri("http://test.com"), new BasicHttpClient()), Throws.TypeOf <ArgumentNullException>().And.Property("ParamName").EqualTo("code")); Assert.That( async() => await oidcLoginHandler.ProcessOidcAuthorizationResponse("EDC", string.Empty, new OpenIdConnectAuthorizationState(), new Uri("http://test.com"), new BasicHttpClient()), Throws.TypeOf <ArgumentNullException>().And.Property("ParamName").EqualTo("code")); Assert.That( async() => await oidcLoginHandler.ProcessOidcAuthorizationResponse("EDC", "code", null, new Uri("http://test.com"), new BasicHttpClient()), Throws.TypeOf <ArgumentNullException>().And.Property("ParamName").EqualTo("authState")); Assert.That( async() => await oidcLoginHandler.ProcessOidcAuthorizationResponse("EDC", "code", new OpenIdConnectAuthorizationState(), null, new BasicHttpClient()), Throws.TypeOf <ArgumentNullException>().And.Property("ParamName").EqualTo("requestBaseUrl")); Assert.That( async() => await oidcLoginHandler.ProcessOidcAuthorizationResponse("EDC", "code", new OpenIdConnectAuthorizationState(), new Uri("http://test.com"), null), Throws.TypeOf <ArgumentNullException>().And.Property("ParamName").EqualTo("httpClient")); }
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(); }
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>()); }
// 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()); } }