//----------------------------------------------------------------------- // // Copyright (c) Outercurve Foundation. All rights reserved. // //----------------------------------------------------------------------- namespace RelyingPartyLogic { using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Web; using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.OAuth2; using DotNetOpenAuth.OAuth2.ChannelElements; using DotNetOpenAuth.OAuth2.Messages; /// /// Provides OAuth 2.0 authorization server information to DotNetOpenAuth. /// public class OAuthAuthorizationServer : IAuthorizationServerHost { private static readonly RSACryptoServiceProvider SigningKey = new RSACryptoServiceProvider(); private readonly INonceStore nonceStore = new NonceDbStore(); /// /// Initializes a new instance of the class. /// public OAuthAuthorizationServer() { this.CryptoKeyStore = new RelyingPartyApplicationDbStore(); } #region IAuthorizationServerHost Members public ICryptoKeyStore CryptoKeyStore { get; private set; } /// /// Gets the authorization code nonce store to use to ensure that authorization codes can only be used once. /// /// The authorization code nonce store. public INonceStore NonceStore { get { return this.nonceStore; } } /// /// Gets the crypto service provider with the asymmetric private key to use for signing access tokens. /// /// /// Must not be null, and must contain the private key. /// /// A crypto service provider instance that contains the private key. public RSACryptoServiceProvider AccessTokenSigningKey { get { return SigningKey; } } /// /// Obtains parameters to go into the formulation of an access token. /// /// Details regarding the resources that the access token will grant access to, and the identity of the client /// that will receive that access. /// Based on this information the receiving resource server can be determined and the lifetime of the access /// token can be set based on the sensitivity of the resources. /// /// A non-null parameters instance that DotNetOpenAuth will dispose after it has been used. /// public AccessTokenResult CreateAccessToken(IAccessTokenRequest accessTokenRequestMessage) { var accessToken = new AuthorizationServerAccessToken() { // For this sample, we assume just one resource server. // If this authorization server needs to mint access tokens for more than one resource server, // we'd look at the request message passed to us and decide which public key to return. ResourceServerEncryptionKey = OAuthResourceServer.CreateRSA(), }; var result = new AccessTokenResult(accessToken); return result; } /// /// Gets the client with a given identifier. /// /// The client identifier. /// The client registration. Never null. /// Thrown when no client with the given identifier is registered with this authorization server. public IClientDescription GetClient(string clientIdentifier) { try { return Database.DataContext.Clients.First(c => c.ClientIdentifier == clientIdentifier); } catch (InvalidOperationException ex) { throw new ArgumentOutOfRangeException("No client by that identifier.", ex); } } /// /// Determines whether a described authorization is (still) valid. /// /// The authorization. /// /// true if the original authorization is still valid; otherwise, false. /// /// /// When establishing that an authorization is still valid, /// it's very important to only match on recorded authorizations that /// meet these criteria: /// 1) The client identifier matches. /// 2) The user account matches. /// 3) The scope on the recorded authorization must include all scopes in the given authorization. /// 4) The date the recorded authorization was issued must be no later that the date the given authorization was issued. /// One possible scenario is where the user authorized a client, later revoked authorization, /// and even later reinstated authorization. This subsequent recorded authorization /// would not satisfy requirement #4 in the above list. This is important because the revocation /// the user went through should invalidate all previously issued tokens as a matter of /// security in the event the user was revoking access in order to sever authorization on a stolen /// account or piece of hardware in which the tokens were stored. /// public bool IsAuthorizationValid(IAuthorizationDescription authorization) { return this.IsAuthorizationValid(authorization.Scope, authorization.ClientIdentifier, authorization.UtcIssued, authorization.User); } /// /// Determines whether a given set of resource owner credentials is valid based on the authorization server's user database /// and if so records an authorization entry such that subsequent calls to would /// return true. /// /// Username on the account. /// The user's password. /// /// The access request the credentials came with. /// This may be useful if the authorization server wishes to apply some policy based on the client that is making the request. /// /// /// Receives the canonical username (normalized for the resource server) of the user, for valid credentials; /// Or null if the return value is false. /// /// /// true if the given credentials are valid and the authorization granted; otherwise, false. /// /// /// May be thrown if the authorization server does not support the resource owner password credential grant type. /// public bool TryAuthorizeResourceOwnerCredentialGrant(string userName, string password, IAccessTokenRequest accessRequest, out string canonicalUserName) { // This web site delegates user authentication to OpenID Providers, and as such no users have local passwords with this server. throw new NotSupportedException(); } /// /// Determines whether an access token request given a client credential grant should be authorized /// and if so records an authorization entry such that subsequent calls to would /// return true. /// /// /// The access request the credentials came with. /// This may be useful if the authorization server wishes to apply some policy based on the client that is making the request. /// /// /// true if the given credentials are valid and the authorization granted; otherwise, false. /// /// /// May be thrown if the authorization server does not support the client credential grant type. /// public bool TryAuthorizeClientCredentialsGrant(IAccessTokenRequest accessRequest) { throw new NotImplementedException(); } #endregion public bool CanBeAutoApproved(EndUserAuthorizationRequest authorizationRequest) { if (authorizationRequest == null) { throw new ArgumentNullException("authorizationRequest"); } // NEVER issue an auto-approval to a client that would end up getting an access token immediately // (without a client secret), as that would allow ANY client to spoof an approved client's identity // and obtain unauthorized access to user data. if (authorizationRequest.ResponseType == EndUserAuthorizationResponseType.AuthorizationCode) { // Never issue auto-approval if the client secret is blank, since that too makes it easy to spoof // a client's identity and obtain unauthorized access. var requestingClient = Database.DataContext.Clients.First(c => c.ClientIdentifier == authorizationRequest.ClientIdentifier); if (!string.IsNullOrEmpty(requestingClient.ClientSecret)) { return this.IsAuthorizationValid( authorizationRequest.Scope, authorizationRequest.ClientIdentifier, DateTime.UtcNow, HttpContext.Current.User.Identity.Name); } } // Default to not auto-approving. return false; } private bool IsAuthorizationValid(HashSet requestedScopes, string clientIdentifier, DateTime issuedUtc, string username) { var grantedScopeStrings = from auth in Database.DataContext.ClientAuthorizations where auth.Client.ClientIdentifier == clientIdentifier && auth.CreatedOnUtc <= issuedUtc && (!auth.ExpirationDateUtc.HasValue || auth.ExpirationDateUtc.Value >= DateTime.UtcNow) && auth.User.AuthenticationTokens.Any(token => token.ClaimedIdentifier == username) select auth.Scope; if (!grantedScopeStrings.Any()) { // No granted authorizations prior to the issuance of this token, so it must have been revoked. // Even if later authorizations restore this client's ability to call in, we can't allow // access tokens issued before the re-authorization because the revoked authorization should // effectively and permanently revoke all access and refresh tokens. return false; } var grantedScopes = new HashSet(OAuthUtilities.ScopeStringComparer); foreach (string scope in grantedScopeStrings) { grantedScopes.UnionWith(OAuthUtilities.SplitScopes(scope)); } return requestedScopes.IsSubsetOf(grantedScopes); } } }