172 lines
8.7 KiB
C#
172 lines
8.7 KiB
C#
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<ICryptoKeyStore>(); }
|
|
}
|
|
|
|
public INonceStore NonceStore
|
|
{
|
|
get { return ServiceLocator.Current.GetInstance<INonceStore>(); }
|
|
}
|
|
|
|
public AuthorizationServerHost()
|
|
{
|
|
this.clientRepository = ServiceLocator.Current.GetInstance<IClientRepository>();
|
|
this.clientAuthorizationRepository = ServiceLocator.Current.GetInstance<IClientAuthorizationRepository>();
|
|
}
|
|
|
|
// 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<string> 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<string>(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();
|
|
}
|
|
}
|
|
} |