using System; using System.Collections.Generic; using System.Configuration; using System.Linq; using System.Security.Cryptography; using System.Web; using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.OAuth2; using DotNetOpenAuth.OAuth2.ChannelElements; using DotNetOpenAuth.OAuth2.Messages; using Microsoft.Practices.ServiceLocation; using Taloyhtio.GeneralSSO.Server.CodeFiles.Common; using Taloyhtio.GeneralSSO.Server.CodeFiles.Repositories; namespace Taloyhtio.GeneralSSO.Server.CodeFiles.Infrastructure.OAuth { public class AuthorizationServerHost : IAuthorizationServerHost { private IClientRepository clientRepository; private IClientAuthorizationRepository clientAuthorizationRepository; public ICryptoKeyStore CryptoKeyStore { get { return ServiceLocator.Current.GetInstance(); } } public INonceStore NonceStore { get { return ServiceLocator.Current.GetInstance(); } } public AuthorizationServerHost() { this.clientRepository = ServiceLocator.Current.GetInstance(); this.clientAuthorizationRepository = ServiceLocator.Current.GetInstance(); } // Create access token: // 1. Authorization server: signs token with auth server's private key (cert contains both private and public key) // 2. Authorization server: encrypts token with resource server's public key only (cert contains public key only) // 3. Resource server: decrypts token with resource server's private key (cert contains both private and public key) // 4. Resource server: validates token with auth server's public key (cert contains public key only) // In all-in-one server environments auth and res certificates contain both private and public keys public AccessTokenResult CreateAccessToken(IAccessTokenRequest accessTokenRequestMessage) { var accessToken = new AuthorizationServerAccessToken(); // Just for the sake of the sample, we use a short-lived token. This can be useful to mitigate the security risks // of access tokens that are used over standard HTTP. // But this is just the lifetime of the access token. The client can still renew it using their refresh token until // the authorization itself expires. int lifetimeMinutes = this.getTokenLifetime(); accessToken.Lifetime = TimeSpan.FromMinutes(lifetimeMinutes); // Also take into account the remaining life of the authorization and artificially shorten the access token's lifetime // to account for that if necessary. // 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. accessToken.ResourceServerEncryptionKey = (RSACryptoServiceProvider)Cert.ResourceServerEncyptionCertificate.PublicKey.Key; accessToken.AccessTokenSigningKey = (RSACryptoServiceProvider)Cert.AuthServerSigningCertificate.PrivateKey; var result = new AccessTokenResult(accessToken); return result; } private int getTokenLifetime() { string lifeTimeStr = ConfigurationManager.AppSettings["AccessTokenLifeTimeMinutes"]; if (string.IsNullOrEmpty(lifeTimeStr)) { return Constants.OAuth.DEFAULT_TOKEN_LIFETIME_MINUTES; } int lifeTime; if (!int.TryParse(lifeTimeStr, out lifeTime)) { return Constants.OAuth.DEFAULT_TOKEN_LIFETIME_MINUTES; } return lifeTime; } public IClientDescription GetClient(string clientIdentifier) { // var consumerRow = ServerContext.DataContext.Clients.SingleOrDefault( // consumerCandidate => consumerCandidate.ClientIdentifier == clientIdentifier); // if (consumerRow == null) // { // throw new ArgumentOutOfRangeException("clientIdentifier"); // } // // return consumerRow; var client = this.clientRepository.GetByClientId(clientIdentifier); if (client == null) { throw new Exception(string.Format("Client '{0}' not registered.", clientIdentifier)); } return client; } public bool IsAuthorizationValid(IAuthorizationDescription authorization) { return this.isAuthorizationValid(authorization.Scope, authorization.ClientIdentifier, authorization.UtcIssued, authorization.User); } private bool isAuthorizationValid(HashSet requestedScopes, string clientIdentifier, DateTime issuedUtc, string username) { // If db precision exceeds token time precision (which is common), the following query would // often disregard a token that is minted immediately after the authorization record is stored in the db. // To compensate for this, we'll increase the timestamp on the token's issue date by 1 second. issuedUtc += TimeSpan.FromSeconds(1); // var grantedScopeStrings = from auth in ServerContext.DataContext.ClientAuthorizations // where // auth.Client.ClientIdentifier == clientIdentifier && // auth.CreatedOnUtc <= issuedUtc && // (!auth.ExpirationDateUtc.HasValue || auth.ExpirationDateUtc.Value >= DateTime.UtcNow) && // auth.User.OpenIDClaimedIdentifier == username // select auth.Scope; var grantedScopeStrings = this.clientAuthorizationRepository.GetNotExpiredBy(clientIdentifier, username, issuedUtc).Select(c => c.Scope); if (grantedScopeStrings.IsNullOrEmpty()) { // 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); grantedScopeStrings.ToList().ForEach(c => grantedScopes.UnionWith(OAuthUtilities.SplitScopes(c))); return requestedScopes.IsSubsetOf(grantedScopes); } /*public bool CanBeAutoApproved(EndUserAuthorizationRequest authRequest, string userName) { if (authRequest == null || string.IsNullOrEmpty(userName)) { return false; } // 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 arbitrary clients to masquarade as an approved client // and obtain unauthorized access to user data. if (authRequest.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 = ServerContext.DataContext.Clients.First(c => c.ClientIdentifier == authorizationRequest.ClientIdentifier); var requestingClient = this.clientRepository.GetBy(authRequest.ClientIdentifier); if (requestingClient == null || string.IsNullOrEmpty(requestingClient.ClientSecret)) { return false; } return this.isAuthorizationValid(authRequest.Scope, authRequest.ClientIdentifier, DateTime.UtcNow, userName); } // Default to not auto-approving. return false; }*/ public AutomatedAuthorizationCheckResponse CheckAuthorizeClientCredentialsGrant(IAccessTokenRequest accessRequest) { throw new NotImplementedException(); } public AutomatedUserAuthorizationCheckResponse CheckAuthorizeResourceOwnerCredentialGrant(string userName, string password, IAccessTokenRequest accessRequest) { throw new NotImplementedException(); } } }