/// <summary> /// Generates the authentication requests that can satisfy the requirements of some OpenID Identifier. /// </summary> /// <param name="userSuppliedIdentifier"> /// The Identifier supplied by the user. This may be a URL, an XRI or i-name. /// </param> /// <returns> /// A sequence of authentication requests, any of which constitutes a valid identity assertion on the Claimed Identifier. /// Never null, but may be empty. /// </returns> /// <remarks> /// <para>Any individual generated request can satisfy the authentication. /// The generated requests are sorted in preferred order. /// Each request is generated as it is enumerated to. Associations are created only as /// <see cref="IAuthenticationRequest.RedirectingResponse"/> is called.</para> /// <para>No exception is thrown if no OpenID endpoints were discovered. /// An empty enumerable is returned instead.</para> /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para> /// </remarks> /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception> public IEnumerable <IAuthenticationRequest> CreateRequests(Identifier userSuppliedIdentifier) { Requires.NotNull(userSuppliedIdentifier, "userSuppliedIdentifier"); RequiresEx.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); return(this.CreateRequests(userSuppliedIdentifier, Realm.AutoDetect)); }
/// <summary> /// Gets the actual arguments to add to a querystring or other response, /// where type URI, alias, and actual key/values are all defined. /// </summary> /// <param name="includeOpenIdPrefix"> /// <c>true</c> if the generated parameter names should include the 'openid.' prefix. /// This should be <c>true</c> for all but direct response messages. /// </param> /// <returns>A dictionary of key=value pairs to add to the message to carry the extension.</returns> internal IDictionary <string, string> GetArgumentsToSend(bool includeOpenIdPrefix) { RequiresEx.ValidState(!this.ReadMode); Dictionary <string, string> args = new Dictionary <string, string>(); foreach (var typeUriAndExtension in this.extensions) { string typeUri = typeUriAndExtension.Key; var extensionArgs = typeUriAndExtension.Value; if (extensionArgs.Count == 0) { continue; } string alias = this.aliasManager.GetAlias(typeUri); // send out the alias declaration string openidPrefix = includeOpenIdPrefix ? this.protocol.openid.Prefix : string.Empty; args.Add(openidPrefix + this.protocol.openidnp.ns + "." + alias, typeUri); string prefix = openidPrefix + alias; foreach (var pair in extensionArgs) { string key = prefix; if (pair.Key.Length > 0) { key += "." + pair.Key; } args.Add(key, pair.Value); } } return(args); }
/// <summary> /// Generates the authentication requests that can satisfy the requirements of some OpenID Identifier. /// </summary> /// <param name="userSuppliedIdentifier">The Identifier supplied by the user. This may be a URL, an XRI or i-name.</param> /// <param name="realm">The shorest URL that describes this relying party web site's address. /// For example, if your login page is found at https://www.example.com/login.aspx, /// your realm would typically be https://www.example.com/.</param> /// <param name="requestContext">The request context.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns> /// A sequence of authentication requests, any of which constitutes a valid identity assertion on the Claimed Identifier. /// Never null, but may be empty. /// </returns> /// <exception cref="InvalidOperationException">Thrown if <see cref="HttpContext.Current">HttpContext.Current</see> == <c>null</c>.</exception> /// <remarks> /// <para>Any individual generated request can satisfy the authentication. /// The generated requests are sorted in preferred order. /// Each request is generated as it is enumerated to. Associations are created only as /// <see cref="IAuthenticationRequest.GetRedirectingResponseAsync" /> is called.</para> /// <para>No exception is thrown if no OpenID endpoints were discovered. /// An empty enumerable is returned instead.</para> /// <para>Requires an <see cref="HttpContext.Current">HttpContext.Current</see> context.</para> /// </remarks> public async Task <IEnumerable <IAuthenticationRequest> > CreateRequestsAsync(Identifier userSuppliedIdentifier, Realm realm, HttpRequestBase requestContext = null, CancellationToken cancellationToken = default(CancellationToken)) { RequiresEx.ValidState(requestContext != null || (HttpContext.Current != null && HttpContext.Current.Request != null), MessagingStrings.HttpContextRequired); Requires.NotNull(userSuppliedIdentifier, "userSuppliedIdentifier"); Requires.NotNull(realm, "realm"); requestContext = requestContext ?? this.channel.GetRequestFromContext(); // This next code contract is a BAD idea, because it causes each authentication request to be generated // at least an extra time. //// // Build the return_to URL UriBuilder returnTo = new UriBuilder(requestContext.GetPublicFacingUrl()); // Trim off any parameters with an "openid." prefix, and a few known others // to avoid carrying state from a prior login attempt. returnTo.Query = string.Empty; NameValueCollection queryParams = requestContext.GetQueryStringBeforeRewriting(); var returnToParams = new Dictionary <string, string>(queryParams.Count); foreach (string key in queryParams) { if (!IsOpenIdSupportingParameter(key) && key != null) { returnToParams.Add(key, queryParams[key]); } } returnTo.AppendQueryArgs(returnToParams); return(await this.CreateRequestsAsync(userSuppliedIdentifier, realm, returnTo.Uri, cancellationToken)); }
/// <summary> /// Gets the OAuth authorization request included with an OpenID authentication /// request, if there is one. /// </summary> /// <param name="openIdRequest">The OpenID authentication request.</param> /// <returns> /// The scope of access the relying party is requesting, or null if no OAuth request /// is present. /// </returns> /// <remarks> /// <para>Call this method rather than simply extracting the OAuth extension /// out from the authentication request directly to ensure that the additional /// security measures that are required are taken.</para> /// </remarks> public AuthorizationRequest ReadAuthorizationRequest(IHostProcessedRequest openIdRequest) { Requires.NotNull(openIdRequest, "openIdRequest"); RequiresEx.ValidState(this.TokenManager is ICombinedOpenIdProviderTokenManager); var openidTokenManager = this.TokenManager as ICombinedOpenIdProviderTokenManager; ErrorUtilities.VerifyOperation(openidTokenManager != null, OAuthStrings.OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface, typeof(IOpenIdOAuthTokenManager).FullName); var authzRequest = openIdRequest.GetExtension <AuthorizationRequest>(); if (authzRequest == null) { return(null); } // OpenID+OAuth spec section 9: // The Combined Provider SHOULD verify that the consumer key passed in the // request is authorized to be used for the realm passed in the request. string expectedConsumerKey = openidTokenManager.GetConsumerKey(openIdRequest.Realm); ErrorUtilities.VerifyProtocol( string.Equals(expectedConsumerKey, authzRequest.Consumer, StringComparison.Ordinal), OAuthStrings.OpenIdOAuthRealmConsumerKeyDoNotMatch); return(authzRequest); }
public void AttachAuthorizationResponse(IHostProcessedRequest openIdAuthenticationRequest, string scope) { Requires.NotNull(openIdAuthenticationRequest, "openIdAuthenticationRequest"); RequiresEx.ValidState(this.TokenManager is ICombinedOpenIdProviderTokenManager); var openidTokenManager = this.TokenManager as ICombinedOpenIdProviderTokenManager; IOpenIdMessageExtension response; if (scope != null) { // Generate an authorized request token to return to the relying party. string consumerKey = openidTokenManager.GetConsumerKey(openIdAuthenticationRequest.Realm); var approvedResponse = new AuthorizationApprovedResponse { RequestToken = this.TokenGenerator.GenerateRequestToken(consumerKey), Scope = scope, }; openidTokenManager.StoreOpenIdAuthorizedRequestToken(consumerKey, approvedResponse); response = approvedResponse; } else { response = new AuthorizationDeclinedResponse(); } openIdAuthenticationRequest.AddResponseExtension(response); }
/// <summary> /// Gets the response to send to the user agent. /// </summary> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The response.</returns> /// <exception cref="InvalidOperationException">Thrown if <see cref="IsResponseReady" /> is <c>false</c>.</exception> internal async Task <IProtocolMessage> GetResponseAsync(CancellationToken cancellationToken) { RequiresEx.ValidState(this.IsResponseReady, OpenIdStrings.ResponseNotReady); if (this.responseExtensions.Count > 0) { var responseMessage = await this.GetResponseMessageAsync(cancellationToken); var extensibleResponse = responseMessage as IProtocolMessageWithExtensions; ErrorUtilities.VerifyOperation(extensibleResponse != null, MessagingStrings.MessageNotExtensible, responseMessage.GetType().Name); foreach (var extension in this.responseExtensions) { // It's possible that a prior call to this property // has already added some/all of the extensions to the message. // We don't have to worry about deleting old ones because // this class provides no facility for removing extensions // that are previously added. if (!extensibleResponse.Extensions.Contains(extension)) { extensibleResponse.Extensions.Add(extension); } } } return(await this.GetResponseMessageAsync(cancellationToken)); }
/// <summary> /// Discovers what access the client should have considering the access token in the current request. /// </summary> /// <param name="httpRequestInfo">The HTTP request info.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="requiredScopes">The set of scopes required to approve this request.</param> /// <returns> /// The access token describing the authorization the client has. Never <c>null</c>. /// </returns> /// <exception cref="ProtocolFaultResponseException">Thrown when the client is not authorized. This exception should be caught and the /// <see cref="ProtocolFaultResponseException.ErrorResponseMessage" /> message should be returned to the client.</exception> public virtual Task <AccessToken> GetAccessTokenAsync(HttpRequestBase httpRequestInfo = null, CancellationToken cancellationToken = default(CancellationToken), params string[] requiredScopes) { Requires.NotNull(requiredScopes, "requiredScopes"); RequiresEx.ValidState(this.ScopeSatisfiedCheck != null, Strings.RequiredPropertyNotYetPreset); httpRequestInfo = httpRequestInfo ?? this.Channel.GetRequestFromContext(); return(this.GetAccessTokenAsync(httpRequestInfo.AsHttpRequestMessage(), cancellationToken, requiredScopes)); }
/// <summary> /// Generates a URL that the user's browser can be directed to in order to authorize /// this client to access protected data at some resource server. /// </summary> /// <param name="authorization">The authorization state that is tracking this particular request. Optional.</param> /// <param name="implicitResponseType"> /// <c>true</c> to request an access token in the fragment of the response's URL; /// <c>false</c> to authenticate to the authorization server and acquire the access token (and possibly a refresh token) via a private channel. /// </param> /// <param name="state">The client state that should be returned with the authorization response.</param> /// <returns> /// A fully-qualified URL suitable to initiate the authorization flow. /// </returns> public Uri RequestUserAuthorization(IAuthorizationState authorization, bool implicitResponseType = false, string state = null) { Requires.NotNull(authorization, "authorization"); RequiresEx.ValidState(!string.IsNullOrEmpty(this.ClientIdentifier)); var request = this.PrepareRequestUserAuthorization(authorization, implicitResponseType, state); return(this.Channel.PrepareResponse(request).GetDirectUriRequest(this.Channel)); }
/// <summary> /// Gets the fields carried by a given OpenId extension. /// </summary> /// <param name="extensionTypeUri">The type URI of the extension whose fields are being queried for.</param> /// <returns> /// The fields included in the given extension, or null if the extension is not present. /// </returns> internal IDictionary <string, string> GetExtensionArguments(string extensionTypeUri) { Requires.NotNullOrEmpty(extensionTypeUri, "extensionTypeUri"); RequiresEx.ValidState(this.ReadMode); IDictionary <string, string> extensionArgs; this.extensions.TryGetValue(extensionTypeUri, out extensionArgs); return(extensionArgs); }
/// <summary> /// Generates a URL that the user's browser can be directed to in order to authorize /// this client to access protected data at some resource server. /// </summary> /// <param name="authorization">The authorization state that is tracking this particular request. Optional.</param> /// <param name="implicitResponseType"><c>true</c> to request an access token in the fragment of the response's URL; /// <c>false</c> to authenticate to the authorization server and acquire the access token (and possibly a refresh token) via a private channel.</param> /// <param name="state">The client state that should be returned with the authorization response.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns> /// A fully-qualified URL suitable to initiate the authorization flow. /// </returns> public async Task <Uri> RequestUserAuthorizationAsync(IAuthorizationState authorization, bool implicitResponseType = false, string state = null, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(authorization, "authorization"); RequiresEx.ValidState(!string.IsNullOrEmpty(this.ClientIdentifier)); var request = this.PrepareRequestUserAuthorization(authorization, implicitResponseType, state); var response = await this.Channel.PrepareResponseAsync(request, cancellationToken); return(response.GetDirectUriRequest()); }
public void AttachAuthorizationResponse(IHostProcessedRequest openIdAuthenticationRequest, string consumerKey, string scope) { Requires.NotNull(openIdAuthenticationRequest, "openIdAuthenticationRequest"); Requires.That((consumerKey == null) == (scope == null), null, "consumerKey and scope must either be both provided or both omitted."); RequiresEx.ValidState(this.TokenManager is ICombinedOpenIdProviderTokenManager); var openidTokenManager = (ICombinedOpenIdProviderTokenManager)this.TokenManager; ErrorUtilities.VerifyArgument(consumerKey == null || consumerKey == openidTokenManager.GetConsumerKey(openIdAuthenticationRequest.Realm), OAuthStrings.OpenIdOAuthRealmConsumerKeyDoNotMatch); this.AttachAuthorizationResponse(openIdAuthenticationRequest, scope); }
/// <summary> /// Sends an identity assertion on behalf of one of this Provider's /// members in order to redirect the user agent to a relying party /// web site and log him/her in immediately in one uninterrupted step. /// </summary> /// <param name="providerEndpoint">The absolute URL on the Provider site that receives OpenID messages.</param> /// <param name="relyingPartyRealm">The URL of the Relying Party web site. /// This will typically be the home page, but may be a longer URL if /// that Relying Party considers the scope of its realm to be more specific. /// The URL provided here must allow discovery of the Relying Party's /// XRDS document that advertises its OpenID RP endpoint.</param> /// <param name="claimedIdentifier">The Identifier you are asserting your member controls.</param> /// <param name="localIdentifier">The Identifier you know your user by internally. This will typically /// be the same as <paramref name="claimedIdentifier"/>.</param> /// <param name="extensions">The extensions.</param> public void SendUnsolicitedAssertion(Uri providerEndpoint, Realm relyingPartyRealm, Identifier claimedIdentifier, Identifier localIdentifier, params IExtensionMessage[] extensions) { RequiresEx.ValidState(HttpContext.Current != null, MessagingStrings.HttpContextRequired); Requires.NotNull(providerEndpoint, "providerEndpoint"); Requires.That(providerEndpoint.IsAbsoluteUri, "providerEndpoint", OpenIdStrings.AbsoluteUriRequired); Requires.NotNull(relyingPartyRealm, "relyingPartyRealm"); Requires.NotNull(claimedIdentifier, "claimedIdentifier"); Requires.NotNull(localIdentifier, "localIdentifier"); this.PrepareUnsolicitedAssertion(providerEndpoint, relyingPartyRealm, claimedIdentifier, localIdentifier, extensions).Send(); }
public void Respond(IRequest request) { RequiresEx.ValidState(HttpContext.Current != null, MessagingStrings.CurrentHttpContextRequired); Requires.NotNull(request, "request"); Requires.That(request.IsResponseReady, "request", OpenIdStrings.ResponseNotReady); this.ApplyBehaviorsToResponse(request); Request requestInternal = (Request)request; this.Channel.Respond(requestInternal.Response); }
/// <summary> /// Discovers what access the client should have considering the access token in the current request. /// </summary> /// <param name="httpRequestInfo">The HTTP request info.</param> /// <param name="requiredScopes">The set of scopes required to approve this request.</param> /// <returns> /// The access token describing the authorization the client has. Never <c>null</c>. /// </returns> /// <exception cref="ProtocolFaultResponseException"> /// Thrown when the client is not authorized. This exception should be caught and the /// <see cref="ProtocolFaultResponseException.ErrorResponseMessage"/> message should be returned to the client. /// </exception> public virtual AccessToken GetAccessToken(HttpRequestBase httpRequestInfo = null, params string[] requiredScopes) { Requires.NotNull(requiredScopes, "requiredScopes"); RequiresEx.ValidState(this.ScopeSatisfiedCheck != null, Strings.RequiredPropertyNotYetPreset); if (httpRequestInfo == null) { httpRequestInfo = this.Channel.GetRequestFromContext(); } AccessToken accessToken; AccessProtectedResourceRequest request = null; try { if (this.Channel.TryReadFromRequest <AccessProtectedResourceRequest>(httpRequestInfo, out request)) { accessToken = this.AccessTokenAnalyzer.DeserializeAccessToken(request, request.AccessToken); ErrorUtilities.VerifyHost(accessToken != null, "IAccessTokenAnalyzer.DeserializeAccessToken returned a null reslut."); if (string.IsNullOrEmpty(accessToken.User) && string.IsNullOrEmpty(accessToken.ClientIdentifier)) { Logger.OAuth.Error("Access token rejected because both the username and client id properties were null or empty."); ErrorUtilities.ThrowProtocol(ResourceServerStrings.InvalidAccessToken); } var requiredScopesSet = OAuthUtilities.ParseScopeSet(requiredScopes); if (!this.ScopeSatisfiedCheck.IsScopeSatisfied(requiredScope: requiredScopesSet, grantedScope: accessToken.Scope)) { var response = UnauthorizedResponse.InsufficientScope(request, requiredScopesSet); throw new ProtocolFaultResponseException(this.Channel, response); } return(accessToken); } else { var ex = new ProtocolException(ResourceServerStrings.MissingAccessToken); var response = UnauthorizedResponse.InvalidRequest(ex); throw new ProtocolFaultResponseException(this.Channel, response, innerException: ex); } } catch (ProtocolException ex) { if (ex is ProtocolFaultResponseException) { // This doesn't need to be wrapped again. throw; } var response = request != null?UnauthorizedResponse.InvalidToken(request, ex) : UnauthorizedResponse.InvalidRequest(ex); throw new ProtocolFaultResponseException(this.Channel, response, innerException: ex); } }
/// <summary> /// Prepares a request for user authorization from an authorization server. /// </summary> /// <param name="authorization">The authorization state to associate with this particular request.</param> /// <returns>The authorization request.</returns> public OutgoingWebResponse PrepareRequestUserAuthorization(IAuthorizationState authorization) { Requires.NotNull(authorization, "authorization"); RequiresEx.ValidState(authorization.Callback != null || (HttpContext.Current != null && HttpContext.Current.Request != null), MessagingStrings.HttpContextRequired); RequiresEx.ValidState(!string.IsNullOrEmpty(this.ClientIdentifier), Strings.RequiredPropertyNotYetPreset, "ClientIdentifier"); if (authorization.Callback == null) { authorization.Callback = this.Channel.GetRequestFromContext().GetPublicFacingUrl() .StripMessagePartsFromQueryString(this.Channel.MessageDescriptions.Get(typeof(EndUserAuthorizationSuccessResponseBase), Protocol.Default.Version)) .StripMessagePartsFromQueryString(this.Channel.MessageDescriptions.Get(typeof(EndUserAuthorizationFailedResponse), Protocol.Default.Version)); authorization.SaveChanges(); } var request = new EndUserAuthorizationRequestC(this.AuthorizationServer) { ClientIdentifier = this.ClientIdentifier, Callback = authorization.Callback, }; request.Scope.ResetContents(authorization.Scope); // Mitigate XSRF attacks by including a state value that would be unpredictable between users, but // verifiable for the same user/session. // If the host is implementing the authorization tracker though, they're handling this protection themselves. HttpCookie cookie = null; if (this.AuthorizationTracker == null) { var context = this.Channel.GetHttpContext(); string xsrfKey = MessagingUtilities.GetNonCryptoRandomDataAsBase64(16); cookie = new HttpCookie(XsrfCookieName, xsrfKey) { HttpOnly = true, Secure = FormsAuthentication.RequireSSL, ////Expires = DateTime.Now.Add(OAuth2ClientSection.Configuration.MaxAuthorizationTime), // we prefer session cookies to persistent ones }; request.ClientState = xsrfKey; } var response = this.Channel.PrepareResponse(request); if (cookie != null) { response.Cookies.Add(cookie); } return(response); }
/// <summary> /// Processes the authorization response from an authorization server, if available. /// </summary> /// <param name="request">The incoming HTTP request that may carry an authorization response.</param> /// <returns>The authorization state that contains the details of the authorization.</returns> public IAuthorizationState ProcessUserAuthorization(HttpRequestBase request = null) { RequiresEx.ValidState(!string.IsNullOrEmpty(this.ClientIdentifier), Strings.RequiredPropertyNotYetPreset, "ClientIdentifier"); RequiresEx.ValidState(this.ClientCredentialApplicator != null, Strings.RequiredPropertyNotYetPreset, "ClientCredentialApplicator"); if (request == null) { request = this.Channel.GetRequestFromContext(); } IMessageWithClientState response; if (this.Channel.TryReadFromRequest <IMessageWithClientState>(request, out response)) { Uri callback = MessagingUtilities.StripMessagePartsFromQueryString(request.GetPublicFacingUrl(), this.Channel.MessageDescriptions.Get(response)); IAuthorizationState authorizationState; if (this.AuthorizationTracker != null) { authorizationState = this.AuthorizationTracker.GetAuthorizationState(callback, response.ClientState); ErrorUtilities.VerifyProtocol(authorizationState != null, ClientStrings.AuthorizationResponseUnexpectedMismatch); } else { var context = this.Channel.GetHttpContext(); HttpCookie cookie = request.Cookies[XsrfCookieName]; ErrorUtilities.VerifyProtocol(cookie != null && string.Equals(response.ClientState, cookie.Value, StringComparison.Ordinal), ClientStrings.AuthorizationResponseUnexpectedMismatch); authorizationState = new AuthorizationState { Callback = callback }; } var success = response as EndUserAuthorizationSuccessAuthCodeResponse; var failure = response as EndUserAuthorizationFailedResponse; ErrorUtilities.VerifyProtocol(success != null || failure != null, MessagingStrings.UnexpectedMessageReceivedOfMany); if (success != null) { this.UpdateAuthorizationWithResponse(authorizationState, success); } else // failure { Logger.OAuth.Info("User refused to grant the requested authorization at the Authorization Server."); authorizationState.Delete(); } return(authorizationState); } return(null); }
/// <summary> /// Gets the dictionary of message parts that should be deserialized into extensions. /// </summary> /// <param name="message">The message.</param> /// <param name="ignoreUnsigned">If set to <c>true</c> only signed extensions will be available.</param> /// <returns> /// A dictionary of message parts, including only signed parts when appropriate. /// </returns> private IDictionary <string, string> GetExtensionsDictionary(IProtocolMessage message, bool ignoreUnsigned) { RequiresEx.ValidState(this.Channel != null); IndirectSignedResponse signedResponse = message as IndirectSignedResponse; if (signedResponse != null && ignoreUnsigned) { return(signedResponse.GetSignedMessageParts(this.Channel)); } else { return(this.Channel.MessageDescriptions.GetAccessor(message)); } }
/// <summary> /// Decrypts the specified value using either the symmetric or asymmetric encryption algorithm as appropriate. /// </summary> /// <param name="value">The value.</param> /// <param name="symmetricSecretHandle">The symmetric secret handle. <c>null</c> when using an asymmetric algorithm.</param> /// <returns> /// The decrypted value. /// </returns> private byte[] Decrypt(byte[] value, string symmetricSecretHandle) { RequiresEx.ValidState(this.asymmetricEncrypting != null || symmetricSecretHandle != null); if (this.asymmetricEncrypting != null) { return(this.asymmetricEncrypting.DecryptWithRandomSymmetricKey(value)); } else { var key = this.cryptoKeyStore.GetKey(this.cryptoKeyBucket, symmetricSecretHandle); ErrorUtilities.VerifyProtocol(key != null, MessagingStrings.MissingDecryptionKeyForHandle, this.cryptoKeyBucket, symmetricSecretHandle); return(MessagingUtilities.Decrypt(value, key.Key)); } }
/// <summary> /// Processes the authorization response from an authorization server, if available. /// </summary> /// <param name="request">The incoming HTTP request that may carry an authorization response.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The authorization state that contains the details of the authorization.</returns> public async Task <IAuthorizationState> ProcessUserAuthorizationAsync(HttpRequestMessage request, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(request, "request"); RequiresEx.ValidState(!string.IsNullOrEmpty(this.ClientIdentifier), Strings.RequiredPropertyNotYetPreset, "ClientIdentifier"); RequiresEx.ValidState(this.ClientCredentialApplicator != null, Strings.RequiredPropertyNotYetPreset, "ClientCredentialApplicator"); var response = await this.Channel.TryReadFromRequestAsync <IMessageWithClientState>(request, cancellationToken); if (response != null) { Uri callback = request.RequestUri.StripMessagePartsFromQueryString(this.Channel.MessageDescriptions.Get(response)); IAuthorizationState authorizationState; if (this.AuthorizationTracker != null) { authorizationState = this.AuthorizationTracker.GetAuthorizationState(callback, response.ClientState); ErrorUtilities.VerifyProtocol(authorizationState != null, ClientStrings.AuthorizationResponseUnexpectedMismatch); } else { var xsrfCookieValue = (from cookieHeader in request.Headers.GetCookies() from cookie in cookieHeader.Cookies where cookie.Name == XsrfCookieName select cookie.Value).FirstOrDefault(); ErrorUtilities.VerifyProtocol(xsrfCookieValue != null && string.Equals(response.ClientState, xsrfCookieValue, StringComparison.Ordinal), ClientStrings.AuthorizationResponseUnexpectedMismatch); authorizationState = new AuthorizationState { Callback = callback }; } var success = response as EndUserAuthorizationSuccessAuthCodeResponse; var failure = response as EndUserAuthorizationFailedResponse; ErrorUtilities.VerifyProtocol(success != null || failure != null, MessagingStrings.UnexpectedMessageReceivedOfMany); if (success != null) { await this.UpdateAuthorizationWithResponseAsync(authorizationState, success, cancellationToken); } else // failure { Logger.OAuth.Info("User refused to grant the requested authorization at the Authorization Server."); authorizationState.Delete(); } return(authorizationState); } return(null); }
/// <summary> /// Prepares a request for user authorization from an authorization server. /// </summary> /// <param name="authorization">The authorization state to associate with this particular request.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns> /// The authorization request. /// </returns> public async Task <HttpResponseMessage> PrepareRequestUserAuthorizationAsync(IAuthorizationState authorization, CancellationToken cancellationToken = default(CancellationToken)) { Requires.NotNull(authorization, "authorization"); RequiresEx.ValidState(authorization.Callback != null || (HttpContext.Current != null && HttpContext.Current.Request != null), MessagingStrings.HttpContextRequired); RequiresEx.ValidState(!string.IsNullOrEmpty(this.ClientIdentifier), Strings.RequiredPropertyNotYetPreset, "ClientIdentifier"); if (authorization.Callback == null) { authorization.Callback = this.Channel.GetRequestFromContext().GetPublicFacingUrl() .StripMessagePartsFromQueryString(this.Channel.MessageDescriptions.Get(typeof(EndUserAuthorizationSuccessResponseBase), Protocol.Default.Version)) .StripMessagePartsFromQueryString(this.Channel.MessageDescriptions.Get(typeof(EndUserAuthorizationFailedResponse), Protocol.Default.Version)); authorization.SaveChanges(); } var request = new EndUserAuthorizationRequestC(this.AuthorizationServer) { ClientIdentifier = this.ClientIdentifier, Callback = authorization.Callback, }; request.Scope.ResetContents(authorization.Scope); // Mitigate XSRF attacks by including a state value that would be unpredictable between users, but // verifiable for the same user/session. // If the host is implementing the authorization tracker though, they're handling this protection themselves. var cookies = new List <CookieHeaderValue>(); if (this.AuthorizationTracker == null) { string xsrfKey = MessagingUtilities.GetNonCryptoRandomDataAsBase64(16, useWeb64: true); cookies.Add(new CookieHeaderValue(XsrfCookieName, xsrfKey) { HttpOnly = true, Secure = FormsAuthentication.RequireSSL, }); request.ClientState = xsrfKey; } var response = await this.Channel.PrepareResponseAsync(request, cancellationToken); response.Headers.AddCookies(cookies); return(response); }
/// <summary> /// Generates a URL that the user's browser can be directed to in order to authorize /// this client to access protected data at some resource server. /// </summary> /// <param name="authorization">The authorization state that is tracking this particular request. Optional.</param> /// <param name="implicitResponseType"> /// <c>true</c> to request an access token in the fragment of the response's URL; /// <c>false</c> to authenticate to the authorization server and acquire the access token (and possibly a refresh token) via a private channel. /// </param> /// <param name="state">The client state that should be returned with the authorization response.</param> /// <returns> /// A message to send to the authorization server. /// </returns> internal EndUserAuthorizationRequest PrepareRequestUserAuthorization(IAuthorizationState authorization, bool implicitResponseType = false, string state = null) { Requires.NotNull(authorization, "authorization"); RequiresEx.ValidState(!string.IsNullOrEmpty(this.ClientIdentifier)); if (authorization.Callback == null) { authorization.Callback = new Uri("http://localhost/"); } var request = implicitResponseType ? (EndUserAuthorizationRequest) new EndUserAuthorizationImplicitRequestC(this.AuthorizationServer) : new EndUserAuthorizationRequestC(this.AuthorizationServer); request.ClientIdentifier = this.ClientIdentifier; request.Callback = authorization.Callback; request.ClientState = state; request.Scope.ResetContents(authorization.Scope); return(request); }
private byte[] CalculateSignature(byte[] bytesToSign, string symmetricSecretHandle) { Requires.NotNull(bytesToSign, "bytesToSign"); RequiresEx.ValidState(this.asymmetricSigning != null || this.cryptoKeyStore != null); if (this.asymmetricSigning != null) { using (var hasher = SHA1.Create()) { return(this.asymmetricSigning.SignData(bytesToSign, hasher)); } } else { var key = this.cryptoKeyStore.GetKey(this.cryptoKeyBucket, symmetricSecretHandle); ErrorUtilities.VerifyProtocol(key != null, MessagingStrings.MissingDecryptionKeyForHandle, this.cryptoKeyBucket, symmetricSecretHandle); using (var symmetricHasher = HmacAlgorithms.Create(HmacAlgorithms.HmacSha256, key.Key)) { return(symmetricHasher.ComputeHash(bytesToSign)); } } }
private void GetRequestedClaims(out string required, out string optional) { RequiresEx.ValidState(this.ClaimsRequested != null); var nonEmptyClaimTypes = this.ClaimsRequested.Where(c => c.Name != null); var optionalClaims = from claim in nonEmptyClaimTypes where claim.IsOptional select claim.Name; var requiredClaims = from claim in nonEmptyClaimTypes where !claim.IsOptional select claim.Name; string[] requiredClaimsArray = requiredClaims.ToArray(); string[] optionalClaimsArray = optionalClaims.ToArray(); required = string.Join(" ", requiredClaimsArray); optional = string.Join(" ", optionalClaimsArray); Assumes.True(required != null); Assumes.True(optional != null); }
/// <summary> /// Processes an incoming authorization-granted message from an SP and obtains an access token. /// </summary> /// <param name="openIdAuthenticationResponse">The OpenID authentication response that may be carrying an authorized request token.</param> /// <returns> /// The access token, or null if OAuth authorization was denied by the user or service provider. /// </returns> /// <remarks> /// The access token, if granted, is automatically stored in the <see cref="ConsumerBase.TokenManager"/>. /// The token manager instance must implement <see cref="IOpenIdOAuthTokenManager"/>. /// </remarks> public AuthorizedTokenResponse ProcessUserAuthorization(IAuthenticationResponse openIdAuthenticationResponse) { Requires.NotNull(openIdAuthenticationResponse, "openIdAuthenticationResponse"); RequiresEx.ValidState(this.TokenManager is IOpenIdOAuthTokenManager); var openidTokenManager = this.TokenManager as IOpenIdOAuthTokenManager; ErrorUtilities.VerifyOperation(openidTokenManager != null, OAuthStrings.OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface, typeof(IOpenIdOAuthTokenManager).FullName); // The OAuth extension is only expected in positive assertion responses. if (openIdAuthenticationResponse.Status != AuthenticationStatus.Authenticated) { return(null); } // Retrieve the OAuth extension var positiveAuthorization = openIdAuthenticationResponse.GetExtension <AuthorizationApprovedResponse>(); if (positiveAuthorization == null) { return(null); } // Prepare a message to exchange the request token for an access token. // We are careful to use a v1.0 message version so that the oauth_verifier is not required. var requestAccess = new AuthorizedTokenRequest(this.ServiceProvider.AccessTokenEndpoint, Protocol.V10.Version) { RequestToken = positiveAuthorization.RequestToken, ConsumerKey = this.ConsumerKey, }; // Retrieve the access token and store it in the token manager. openidTokenManager.StoreOpenIdAuthorizedRequestToken(this.ConsumerKey, positiveAuthorization); var grantAccess = this.Channel.Request <AuthorizedTokenResponse>(requestAccess); this.TokenManager.ExpireRequestTokenAndStoreNewAccessToken(this.ConsumerKey, positiveAuthorization.RequestToken, grantAccess.AccessToken, grantAccess.TokenSecret); // Provide the caller with the access token so it may be associated with the user // that is logging in. return(grantAccess); }
/// <summary> /// Gets the value to use for the openid.signed parameter. /// </summary> /// <param name="signedMessage">The signable message.</param> /// <returns> /// A comma-delimited list of parameter names, omitting the 'openid.' prefix, that determines /// the inclusion and order of message parts that will be signed. /// </returns> private string GetSignedParameterOrder(ITamperResistantOpenIdMessage signedMessage) { RequiresEx.ValidState(this.Channel != null); Requires.NotNull(signedMessage, "signedMessage"); Protocol protocol = Protocol.Lookup(signedMessage.Version); MessageDescription description = this.Channel.MessageDescriptions.Get(signedMessage); var signedParts = from part in description.Mapping.Values where (part.RequiredProtection & System.Net.Security.ProtectionLevel.Sign) != 0 && part.GetValue(signedMessage) != null select part.Name; string prefix = Protocol.V20.openid.Prefix; ErrorUtilities.VerifyInternal(signedParts.All(name => name.StartsWith(prefix, StringComparison.Ordinal)), "All signed message parts must start with 'openid.'."); if (this.opSecuritySettings.SignOutgoingExtensions) { // Tack on any ExtraData parameters that start with 'openid.'. List <string> extraSignedParameters = new List <string>(signedMessage.ExtraData.Count); foreach (string key in signedMessage.ExtraData.Keys) { if (key.StartsWith(protocol.openid.Prefix, StringComparison.Ordinal)) { extraSignedParameters.Add(key); } else { Logger.Signatures.DebugFormat("The extra parameter '{0}' will not be signed because it does not start with 'openid.'.", key); } } signedParts = signedParts.Concat(extraSignedParameters); } int skipLength = prefix.Length; string signedFields = string.Join(",", signedParts.Select(name => name.Substring(skipLength)).ToArray()); return(signedFields); }
/// <summary> /// Adds Javascript snippets to the page to help the Information Card selector do its work, /// or to downgrade gracefully if the user agent lacks an Information Card selector. /// </summary> private void RenderSupportingScript() { RequiresEx.ValidState(this.infoCardSupportedPanel != null); this.Page.ClientScript.RegisterClientScriptResource(typeof(InfoCardSelector), ScriptResourceName); if (this.RenderMode == RenderMode.Static) { this.Page.ClientScript.RegisterStartupScript( typeof(InfoCardSelector), "SelectorSupportingScript_" + this.ClientID, string.Format(CultureInfo.InvariantCulture, "document.infoCard.checkStatic('{0}', '{1}');", this.infoCardSupportedPanel.ClientID, this.infoCardNotSupportedPanel.ClientID), true); } else if (RenderMode == RenderMode.Dynamic) { this.Page.ClientScript.RegisterStartupScript( typeof(InfoCardSelector), "SelectorSupportingScript_" + this.ClientID, string.Format(CultureInfo.InvariantCulture, "document.infoCard.checkDynamic('{0}', '{1}');", this.infoCardSupportedPanel.ClientID, this.infoCardNotSupportedPanel.ClientID), true); } }
/// <summary> /// Adds query parameters for OpenID extensions to the request directed /// at the OpenID provider. /// </summary> /// <param name="extensionTypeUri">The extension type URI.</param> /// <param name="arguments">The arguments for this extension to add to the message.</param> public void AddExtensionArguments(string extensionTypeUri, IDictionary <string, string> arguments) { RequiresEx.ValidState(!this.ReadMode); Requires.NotNullOrEmpty(extensionTypeUri, "extensionTypeUri"); Requires.NotNull(arguments, "arguments"); if (arguments.Count == 0) { return; } IDictionary <string, string> extensionArgs; if (!this.extensions.TryGetValue(extensionTypeUri, out extensionArgs)) { this.extensions.Add(extensionTypeUri, extensionArgs = new Dictionary <string, string>(arguments.Count)); } ErrorUtilities.VerifyProtocol(extensionArgs.Count == 0, OpenIdStrings.ExtensionAlreadyAddedWithSameTypeURI, extensionTypeUri); foreach (var pair in arguments) { extensionArgs.Add(pair.Key, pair.Value); } }
/// <summary> /// Prepares an identity assertion on behalf of one of this Provider's /// members in order to redirect the user agent to a relying party /// web site and log him/her in immediately in one uninterrupted step. /// </summary> /// <param name="providerEndpoint">The absolute URL on the Provider site that receives OpenID messages.</param> /// <param name="relyingPartyRealm">The URL of the Relying Party web site. /// This will typically be the home page, but may be a longer URL if /// that Relying Party considers the scope of its realm to be more specific. /// The URL provided here must allow discovery of the Relying Party's /// XRDS document that advertises its OpenID RP endpoint.</param> /// <param name="claimedIdentifier">The Identifier you are asserting your member controls.</param> /// <param name="localIdentifier">The Identifier you know your user by internally. This will typically /// be the same as <paramref name="claimedIdentifier" />.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="extensions">The extensions.</param> /// <returns> /// A <see cref="HttpResponseMessage" /> object describing the HTTP response to send /// the user agent to allow the redirect with assertion to happen. /// </returns> public async Task <HttpResponseMessage> PrepareUnsolicitedAssertionAsync(Uri providerEndpoint, Realm relyingPartyRealm, Identifier claimedIdentifier, Identifier localIdentifier, CancellationToken cancellationToken = default(CancellationToken), params IExtensionMessage[] extensions) { Requires.NotNull(providerEndpoint, "providerEndpoint"); Requires.That(providerEndpoint.IsAbsoluteUri, "providerEndpoint", OpenIdStrings.AbsoluteUriRequired); Requires.NotNull(relyingPartyRealm, "relyingPartyRealm"); Requires.NotNull(claimedIdentifier, "claimedIdentifier"); Requires.NotNull(localIdentifier, "localIdentifier"); RequiresEx.ValidState(this.Channel.HostFactories != null); // Although the RP should do their due diligence to make sure that this OP // is authorized to send an assertion for the given claimed identifier, // do due diligence by performing our own discovery on the claimed identifier // and make sure that it is tied to this OP and OP local identifier. if (this.SecuritySettings.UnsolicitedAssertionVerification != ProviderSecuritySettings.UnsolicitedAssertionVerificationLevel.NeverVerify) { var serviceEndpoint = IdentifierDiscoveryResult.CreateForClaimedIdentifier(claimedIdentifier, localIdentifier, new ProviderEndpointDescription(providerEndpoint, Protocol.Default.Version), null, null); var discoveredEndpoints = await this.discoveryServices.DiscoverAsync(claimedIdentifier, cancellationToken); if (!discoveredEndpoints.Contains(serviceEndpoint)) { Logger.OpenId.WarnFormat( "Failed to send unsolicited assertion for {0} because its discovered services did not include this endpoint: {1}{2}{1}Discovered endpoints: {1}{3}", claimedIdentifier, Environment.NewLine, serviceEndpoint, discoveredEndpoints.ToStringDeferred(true)); // Only FAIL if the setting is set for it. if (this.securitySettings.UnsolicitedAssertionVerification == ProviderSecuritySettings.UnsolicitedAssertionVerificationLevel.RequireSuccess) { ErrorUtilities.ThrowProtocol(OpenIdStrings.UnsolicitedAssertionForUnrelatedClaimedIdentifier, claimedIdentifier); } } } Logger.OpenId.InfoFormat("Preparing unsolicited assertion for {0}", claimedIdentifier); RelyingPartyEndpointDescription returnToEndpoint = null; var returnToEndpoints = await relyingPartyRealm.DiscoverReturnToEndpointsAsync(this.Channel.HostFactories, true, cancellationToken); if (returnToEndpoints != null) { returnToEndpoint = returnToEndpoints.FirstOrDefault(); } ErrorUtilities.VerifyProtocol(returnToEndpoint != null, OpenIdStrings.NoRelyingPartyEndpointDiscovered, relyingPartyRealm); var positiveAssertion = new PositiveAssertionResponse(returnToEndpoint) { ProviderEndpoint = providerEndpoint, ClaimedIdentifier = claimedIdentifier, LocalIdentifier = localIdentifier, }; if (extensions != null) { foreach (IExtensionMessage extension in extensions) { positiveAssertion.Extensions.Add(extension); } } Reporting.RecordEventOccurrence(this, "PrepareUnsolicitedAssertion"); return(await this.Channel.PrepareResponseAsync(positiveAssertion, cancellationToken)); }
/// <summary> /// Creates a signing element that includes all the signing elements this service provider supports. /// </summary> /// <returns>The created signing element.</returns> internal ITamperProtectionChannelBindingElement CreateTamperProtectionElement() { RequiresEx.ValidState(this.TamperProtectionElements != null); return(new SigningBindingElementChain(this.TamperProtectionElements.Select(el => (ITamperProtectionChannelBindingElement)el.Clone()).ToArray())); }
/// <summary> /// Automatically sends the appropriate response to the user agent /// and signals ASP.NET to short-circuit the page execution pipeline /// now that the response has been completed. /// Not safe to call from ASP.NET web forms. /// </summary> /// <remarks> /// Requires a current HttpContext. /// This call is not safe to make from an ASP.NET web form (.aspx file or code-behind) because /// ASP.NET will render HTML after the protocol message has been sent, which will corrupt the response. /// Use the <see cref="Send()"/> method instead for web forms. /// </remarks> public virtual void Respond() { RequiresEx.ValidState(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); this.Respond(HttpContext.Current); }