/// <summary> /// Validates a ticket contained in the URL, presumably generated by /// the CAS server after a successful authentication. The actual ticket /// validation is performed by the configured TicketValidator /// (i.e., CAS 1.0, CAS 2.0, SAML 1.0). If the validation succeeds, the /// request is authenticated and a FormsAuthenticationCookie and /// corresponding CasAuthenticationTicket are created for the purpose of /// authenticating subsequent requests (see ProcessTicketValidation /// method). If the validation fails, the authentication status remains /// unchanged (generally the user is and remains anonymous). /// </summary> public void ProcessTicketValidation(HttpContextBase httpContext) { HttpApplication app = httpContext.ApplicationInstance; HttpRequestBase request = httpContext.Request; string ticket = request[_casServices.Settings.ArtifactParameterName]; try { // Attempt to authenticate the ticket and resolve to an ICasPrincipal var principal = TicketValidator.Validate(ticket); // Save the ticket in the FormsAuthTicket. Encrypt the ticket and send it as a cookie. var casTicket = new CasAuthenticationTicket( ticket, _urlUtil.RemoveCasArtifactsFromUrl(request.Url.AbsoluteUri), request.UserHostAddress, principal.Assertion, principal.MaxAttributes, _clock.UtcNow ); if (_casServices.ProxyTicketManager != null && !string.IsNullOrEmpty(principal.ProxyGrantingTicket)) { casTicket.ProxyGrantingTicketIou = principal.ProxyGrantingTicket; casTicket.Proxies.AddRange(principal.Proxies); string proxyGrantingTicket = _casServices.ProxyTicketManager.GetProxyGrantingTicket(casTicket.ProxyGrantingTicketIou); if (!string.IsNullOrEmpty(proxyGrantingTicket)) { casTicket.ProxyGrantingTicket = proxyGrantingTicket; } } // TODO: Check the last 2 parameters. We want to take the from/to dates from the FormsAuthenticationTicket. // However, we may need to do some clock drift correction. FormsAuthenticationTicket formsAuthTicket = CreateFormsAuthenticationTicket( principal.Identity.Name, ticket, null, null); SetAuthCookie(httpContext, formsAuthTicket); // Also save the ticket in the server store (if configured) if (_casServices.ServiceTicketManager != null) { _casServices.ServiceTicketManager.UpdateTicketExpiration(casTicket, formsAuthTicket.Expiration); } // Jump directly to EndRequest. Don't allow the Page and/or Handler to execute. // EndRequest will redirect back without the ticket in the URL app.CompleteRequest(); return; } catch (TicketValidationException e) { // Leave principal null. This might not have been a CAS service ticket. Logger.Error(e, "Ticket validation error: {0}", e); } }
/// <summary> /// Attempts to authenticate requests subsequent to the initial authentication /// request (handled by ProcessTicketValidation). This method looks for a /// FormsAuthenticationCookie containing a FormsAuthenticationTicket and attempts /// to confirms its validitiy. It either contains the CAS service ticket or a /// reference to a CasAuthenticationTicket stored in the ServiceTicketManager /// (if configured). If it succeeds, the context.User and Thread.CurrentPrincipal /// are set with a ICasPrincipal and the current request is considered /// authenticated. Otherwise, the current request is effectively anonymous. /// </summary> public void ProcessRequestAuthentication(HttpContextBase httpContext) { // Look for a valid FormsAuthenticationTicket encrypted in a cookie. CasAuthenticationTicket casTicket = null; FormsAuthenticationTicket formsAuthenticationTicket = GetFormsAuthenticationTicket(httpContext); if (formsAuthenticationTicket != null) { ICasPrincipal principal; if (_casServices.ServiceTicketManager != null) { string serviceTicket = formsAuthenticationTicket.UserData; casTicket = _casServices.ServiceTicketManager.GetTicket(serviceTicket); if (casTicket != null) { IAssertion assertion = casTicket.Assertion; if (!_casServices.ServiceTicketManager.VerifyClientTicket(casTicket)) { Logger.Warning("CasAuthenticationTicket failed verification: {0}", casTicket); // Deletes the invalid FormsAuthentication cookie from the client. ClearAuthCookie(httpContext); _casServices.ServiceTicketManager.RevokeTicket(serviceTicket); // Don't give this request a User/Principal. Remove it if it was created // by the underlying FormsAuthenticationModule or another module. principal = null; } else { if (_casServices.ProxyTicketManager != null && !string.IsNullOrEmpty(casTicket.ProxyGrantingTicketIou) && string.IsNullOrEmpty(casTicket.ProxyGrantingTicket)) { string proxyGrantingTicket = _casServices.ProxyTicketManager.GetProxyGrantingTicket(casTicket.ProxyGrantingTicketIou); if (!string.IsNullOrEmpty(proxyGrantingTicket)) { casTicket.ProxyGrantingTicket = proxyGrantingTicket; } } principal = new CasPrincipal(assertion, casTicket.ProxyGrantingTicket, null, casTicket.MaxAttributes); } } else { if (httpContext.User != null && httpContext.User.Identity is FormsIdentity && _authenticationService.GetAuthenticatedUser() != null) { return; } // This didn't resolve to a ticket in the TicketStore. Revoke it. ClearAuthCookie(httpContext); Logger.Debug("Revoking ticket {0}", serviceTicket); _casServices.ServiceTicketManager.RevokeTicket(serviceTicket); // Don't give this request a User/Principal. Remove it if it was created // by the underlying FormsAuthenticationModule or another module. principal = null; } } else { principal = new CasPrincipal(new Assertion(formsAuthenticationTicket.Name)); } httpContext.User = principal; Thread.CurrentPrincipal = principal; if (principal == null) { // Remove the cookie from the client ClearAuthCookie(httpContext); } else { // Extend the expiration of the cookie if FormsAuthentication is configured to do so. if (FormsAuthentication.SlidingExpiration) { FormsAuthenticationTicket newTicket = FormsAuthentication.RenewTicketIfOld(formsAuthenticationTicket); if (newTicket != null && newTicket != formsAuthenticationTicket) { SetAuthCookie(httpContext, newTicket); if (_casServices.ServiceTicketManager != null) { _casServices.ServiceTicketManager.UpdateTicketExpiration(casTicket, newTicket.Expiration); } } } } } }
/// <summary> /// Attempts to connect to the CAS server to retrieve a proxy ticket /// for the target URL specified. /// </summary> /// <remarks> /// Problems retrieving proxy tickets are generally caused by SSL misconfiguration. /// The CAS server must be configured to trust the SSL certificate on the web application's /// server. The CAS server will attempt to establish an SSL connection to this web /// application server to confirm that the proxy ticket request is legitimate. If the /// server does not trust the SSL certificate or the certificate authority/chain of the SSL /// certificate, the request will fail. /// </remarks> /// <param name="httpContext"></param> /// <param name="targetServiceUrl">The target Url to obtain a proxy ticket for</param> /// <returns> /// A proxy ticket for the target Url or an empty string if the request failed. /// </returns> public string GetProxyTicketIdFor(HttpContextBase httpContext, string targetServiceUrl) { Argument.ThrowIfNullOrEmpty(targetServiceUrl, "targetServiceUrl", "targetServiceUrl parameter cannot be null or empty."); if (_casServices.ServiceTicketManager == null) { LogAndThrowConfigurationException("Proxy authentication requires a ServiceTicketManager"); } FormsAuthenticationTicket formsAuthTicket = GetFormsAuthenticationTicket(httpContext); if (formsAuthTicket == null) { LogAndThrowOperationException("The request is not authenticated (does not have a CAS Service or Proxy ticket)."); } if (string.IsNullOrEmpty(formsAuthTicket.UserData)) { LogAndThrowOperationException("The request does not have a CAS Service Ticket."); } CasAuthenticationTicket casTicket = _casServices.ServiceTicketManager.GetTicket(formsAuthTicket.UserData); if (casTicket == null) { LogAndThrowOperationException("The request does not have a valid CAS Service Ticket."); } string proxyTicketResponse = null; try { string proxyUrl = _urlUtil.ConstructProxyTicketRequestUrl(casTicket.ProxyGrantingTicket, targetServiceUrl); proxyTicketResponse = HttpUtil.PerformHttpGet(proxyUrl, true); } catch { LogAndThrowOperationException("Unable to obtain CAS Proxy Ticket."); } if (String.IsNullOrEmpty(proxyTicketResponse)) { LogAndThrowOperationException("Unable to obtain CAS Proxy Ticket (response was empty)"); } string proxyTicket = null; try { ServiceResponse serviceResponse = ServiceResponse.ParseResponse(proxyTicketResponse); if (serviceResponse.IsProxySuccess) { ProxySuccess success = (ProxySuccess)serviceResponse.Item; if (!String.IsNullOrEmpty(success.ProxyTicket)) { Logger.Information("Proxy success: {0}", success.ProxyTicket); } proxyTicket = success.ProxyTicket; } else { ProxyFailure failure = (ProxyFailure)serviceResponse.Item; if (!String.IsNullOrEmpty(failure.Message) && !String.IsNullOrEmpty(failure.Code)) { Logger.Information("Proxy failure: {0} ({1})", failure.Message, failure.Code); } else if (!String.IsNullOrEmpty(failure.Message)) { Logger.Information("Proxy failure: {0}", failure.Message); } else if (!String.IsNullOrEmpty(failure.Code)) { Logger.Information("Proxy failure: Code {0}", failure.Code); } } } catch (InvalidOperationException) { LogAndThrowOperationException("CAS Server response does not conform to CAS 2.0 schema"); } return(proxyTicket); }
/// <summary> /// Validates a ticket contained in the URL, presumably generated by /// the CAS server after a successful authentication. The actual ticket /// validation is performed by the configured TicketValidator /// (i.e., CAS 1.0, CAS 2.0, SAML 1.0). If the validation succeeds, the /// request is authenticated and a FormsAuthenticationCookie and /// corresponding CasAuthenticationTicket are created for the purpose of /// authenticating subsequent requests (see ProcessTicketValidation /// method). If the validation fails, the authentication status remains /// unchanged (generally the user is and remains anonymous). /// </summary> public void ProcessTicketValidation(HttpContextBase httpContext) { HttpApplication app = httpContext.ApplicationInstance; HttpRequestBase request = httpContext.Request; string ticket = request[_casServices.Settings.ArtifactParameterName]; try { // Attempt to authenticate the ticket and resolve to an ICasPrincipal var principal = TicketValidator.Validate(ticket); // Save the ticket in the FormsAuthTicket. Encrypt the ticket and send it as a cookie. var casTicket = new CasAuthenticationTicket( ticket, _urlUtil.RemoveCasArtifactsFromUrl(request.Url.AbsoluteUri), request.UserHostAddress, principal.Assertion, _clock.UtcNow ); if (_casServices.ProxyTicketManager != null && !string.IsNullOrEmpty(principal.ProxyGrantingTicket)) { casTicket.ProxyGrantingTicketIou = principal.ProxyGrantingTicket; casTicket.Proxies.AddRange(principal.Proxies); string proxyGrantingTicket = _casServices.ProxyTicketManager.GetProxyGrantingTicket(casTicket.ProxyGrantingTicketIou); if (!string.IsNullOrEmpty(proxyGrantingTicket)) { casTicket.ProxyGrantingTicket = proxyGrantingTicket; } } // TODO: Check the last 2 parameters. We want to take the from/to dates from the FormsAuthenticationTicket. // However, we may need to do some clock drift correction. FormsAuthenticationTicket formsAuthTicket = CreateFormsAuthenticationTicket( principal.Identity.Name, ticket, null, null); SetAuthCookie(httpContext, formsAuthTicket); // Also save the ticket in the server store (if configured) if (_casServices.ServiceTicketManager != null) { _casServices.ServiceTicketManager.UpdateTicketExpiration(casTicket, formsAuthTicket.Expiration); } // Jump directly to EndRequest. Don't allow the Page and/or Handler to execute. // EndRequest will redirect back without the ticket in the URL app.CompleteRequest(); return; } catch (TicketValidationException e) { // Leave principal null. This might not have been a CAS service ticket. Logger.Error(e, "Ticket validation error: {0}", e); } }