/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
using System;
using System.Globalization;
using System.Net;
using Amazon.Runtime;
using Amazon.Runtime.Internal.Util;
using Amazon.Util;
namespace Amazon.SecurityToken.SAML
{
///
/// Temporary credentials that are created following successful authentication with
/// a federated endpoint supporting SAML.
///
///
/// Currently only the SDK store supports profiles that contain the necessary data to support
/// authentication and role-based credential generation.
///
[Obsolete("This class is obsolete and will be removed in a future release. Please use Amazon.Runtime.FederatedAWSCredentials. Visit http://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/net-dg-config-creds.html for further details.")]
public class StoredProfileSAMLCredentials : RefreshingAWSCredentials
{
private const int MaxAuthenticationRetries = 3;
private RegionEndpoint DefaultSTSClientRegion = RegionEndpoint.USEast1;
private static readonly TimeSpan _preemptExpiryTime = TimeSpan.FromMinutes(5);
private TimeSpan _credentialDuration = MaximumCredentialTimespan;
///
/// Any custom state passed when a credential callback was registered.
///
public object CustomCallbackState { get; set; }
///
/// The minimum allowed timespan for generated credentials, per STS documentation.
///
public static readonly TimeSpan MinimumCredentialTimespan = TimeSpan.FromMinutes(15);
///
/// The maximum allowed timespan for generated credentials, per STS documentation.
///
public static readonly TimeSpan MaximumCredentialTimespan = TimeSpan.FromHours(1);
///
/// Callback signature for obtaining user credentials for authentication demands when
/// the role profile is configured to not use the default identity.
///
///
/// Data about the credential demand including any custom state data that was supplied
/// when the callback was registered.
///
///
/// The network credential to use in user authentication. Return null to signal the user
/// declined to provide credentials and authentication should not proceed.
///
public delegate NetworkCredential RequestUserCredential(CredentialCallbackArgs args);
///
/// Registered callback for obtaining credentials to use in authentication.
/// Required to be set if the role profile is not configured to use the default
/// identity.
///
public RequestUserCredential RequestUserCredentialCallback { get; set; }
#region Public constructors
///
/// Constructs an instance of StoredProfileSAMLCredentials. This constructor searches for details
/// of the role to assume, and optional credentials to use with the endpoint, using the
/// profile name specified in the App.config.
///
public StoredProfileSAMLCredentials()
: this(AWSConfigs.AWSProfileName, null)
{
}
///
///
/// Constructs an instance of StoredProfileSAMLCredentials. After construction call one of the Authenticate
/// methods to authenticate the user/process and obtain temporary AWS credentials.
///
///
/// For users who are domain joined (the role profile does not contain user identity information) the temporary
/// credentials will be refreshed automatically as needed. Non domain-joined users (those with user identity
/// data in the profile) are required to re-authenticate when credential refresh is required. An exception is
/// thrown when attempt is made to refresh credentials in this scenario. The consuming code of this class
/// should catch the exception and prompt the user for credentials, then call Authenticate to re-initialize
/// with a new set of temporary AWS credentials.
///
///
///
/// The name of the profile holding the necessary role data to enable authentication and credential generation.
///
/// Reserved for future use.
/// The ini-format credentials file is not currently supported.
public StoredProfileSAMLCredentials(string profileName, string profilesLocation)
{
this.PreemptExpiryTime = _preemptExpiryTime;
this.CustomCallbackState = null;
var lookupName = string.IsNullOrEmpty(profileName)
? StoredProfileCredentials.DEFAULT_PROFILE_NAME
: profileName;
ProfileName = lookupName;
ProfilesLocation = null;
// If not overriding the credentials lookup location check the SDK Store for credentials. If an override
// is being used then assume the intent is to use the credentials file.
if (string.IsNullOrEmpty(profilesLocation))
{
SAMLRoleProfile profileData;
if (ProfileManager.TryGetProfile(lookupName, out profileData))
{
ProfileData = profileData;
var logger = Logger.GetLogger(typeof(StoredProfileSAMLCredentials));
logger.InfoFormat("SAML role profile found using account name {0} and looking in SDK account store.", lookupName);
}
}
// No credentials found so error out. We do not currently support the ini-format credentials
// file to obtain SAML role profile data.
if (ProfileData == null)
{
var msg = string.Format(CultureInfo.InvariantCulture,
"Profile '{0}' was not found or could not be loaded. Verify that the profile name and data are correct.",
profileName);
throw new ArgumentException(msg);
}
}
#endregion
#region Public properties
///
/// Name of the profile being used.
///
public string ProfileName { get; private set; }
///
/// Location of the profiles, if used.
///
public string ProfilesLocation { get; private set; }
///
/// The data about the SAML endpoint and any required user credentials parsed from the
/// profile.
///
public SAMLRoleProfile ProfileData { get; private set; }
#endregion
///
/// If non-default credentials are to be used for authentication,
/// validates that the authentication required callback has been
/// populated.
///
protected override void Validate()
{
if (!ProfileData.UseDefaultUserIdentity && RequestUserCredentialCallback == null)
throw new CredentialCallbackRequiredException("RequestUserCredentialCallback must be set for profiles that do not use the default user identity for authentication.");
}
///
/// Refresh credentials after expiry. If the role profile is configured to not
/// use the default user identity, an exception is thrown if the UserAuthenticationCallback
/// property has not been set.
///
///
protected override CredentialsRefreshState GenerateNewCredentials()
{
Validate();
CredentialsRefreshState newState = null;
var attempts = 0;
do
{
try
{
NetworkCredential userCredential = null;
if (!ProfileData.UseDefaultUserIdentity)
{
var callbackArgs = new CredentialCallbackArgs
{
UserIdentity = ProfileData.UserIdentity,
CustomState = CustomCallbackState,
PreviousAuthenticationFailed = attempts > 0
};
userCredential = RequestUserCredentialCallback(callbackArgs);
if (userCredential == null) // user declined to authenticate
throw new AuthenticationFailedException("No credentials supplied, credential refresh abandoned");
}
newState = Authenticate(userCredential, _credentialDuration);
}
catch (AuthenticationFailedException)
{
if (attempts < MaxAuthenticationRetries)
attempts++;
else
throw;
}
} while (newState == null && attempts < MaxAuthenticationRetries);
return newState;
}
private CredentialsRefreshState Authenticate(ICredentials userCredential, TimeSpan credentialDuration)
{
CredentialsRefreshState state;
SAMLAssertion assertion;
var configuredRegion = AWSConfigs.AWSRegion;
var region = string.IsNullOrEmpty(configuredRegion)
? DefaultSTSClientRegion
: RegionEndpoint.GetBySystemName(configuredRegion);
try
{
assertion = new SAMLAuthenticationController().GetSAMLAssertion(ProfileData.EndpointSettings.Endpoint.ToString(),
userCredential,
ProfileData.EndpointSettings.AuthenticationType);
}
catch (Exception e)
{
throw new AuthenticationFailedException("Authentication failure, unable to obtain SAML assertion.", e);
}
try
{
using (var stsClient = new AmazonSecurityTokenServiceClient(new AnonymousAWSCredentials(), region))
{
var credentials = assertion.GetRoleCredentials(stsClient, ProfileData.RoleArn, credentialDuration);
state = new CredentialsRefreshState(credentials, stsClient.Config.CorrectedUtcNow + credentialDuration);
}
}
catch (Exception e)
{
var wrappedException = new AmazonClientException("Credential generation failed following successful authentication.", e);
var logger = Logger.GetLogger(typeof(StoredProfileSAMLCredentials));
logger.Error(wrappedException, wrappedException.Message);
throw wrappedException;
}
return state;
}
}
///
/// State class passed on callback to demand user credentials when authentication
/// is performed using a non-default identity.
///
[Obsolete("This class is obsolete and will be removed in a future release. Please update your code to use the Amazon.Runtime.CredentialRequestCallbackArgs class instead.")]
public class CredentialCallbackArgs
{
///
/// Contains the user identity that the user should supply a password
/// for.
///
public string UserIdentity { get; set; }
///
/// Any custom state that was registered with the callback.
///
public object CustomState { get; set; }
///
/// Set if the callback was due to a failed authentication attempt.
/// If false we are beginning to obtain or refresh credentials.
///
public bool PreviousAuthenticationFailed { get; set; }
}
///
/// Exception thrown on validation of a StoredProfileSAMLCredentials instance if the role profile
/// is configured to use a non-default user identity and the QueryUserCredentialCallback on the
/// instance has not been set.
///
[Obsolete("This class is obsolete and will be removed in a future release. Please update your code to use the Amazon.Runtime.CredentialRequestCallbackRequiredException class instead.")]
#if !NETSTANDARD
[Serializable]
#endif
public class CredentialCallbackRequiredException : Exception
{
///
/// Initializes a new exception instance.
///
///
public CredentialCallbackRequiredException(string msg)
: base(msg)
{
}
///
/// Initializes a new exception instance.
///
///
///
public CredentialCallbackRequiredException(string msg, Exception innerException)
: base(msg, innerException)
{
}
///
/// Initializes a new exception instance.
///
///
public CredentialCallbackRequiredException(Exception innerException)
: base(innerException.Message, innerException)
{
}
#if !NETSTANDARD
///
/// Constructs a new instance of the AdfsAuthenticationControllerException class with serialized data.
///
/// The that holds the serialized object data about the exception being thrown.
/// The that contains contextual information about the source or destination.
/// The parameter is null.
/// The class name is null or is zero (0).
protected CredentialCallbackRequiredException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
: base(info, context)
{
}
#endif
}
///
/// Custom exception type thrown when authentication for a user against the
/// configured endpoint fails and a valid SAML assertion document could not be
/// obtained.
///
[Obsolete("This class is obsolete and will be removed in a future release. Please update your code to use the Amazon.Runtime.FederatedAuthenticationFailureException class instead.")]
#if !NETSTANDARD
[Serializable]
#endif
public class AuthenticationFailedException : Exception
{
///
/// Initializes a new exception instance.
///
///
public AuthenticationFailedException(string msg)
: base(msg)
{
}
///
/// Initializes a new exception instance.
///
///
///
public AuthenticationFailedException(string msg, Exception inner)
: base(msg, inner)
{
}
#if !NETSTANDARD
///
/// Constructs a new instance of the AuthenticationFailedException class with serialized data.
///
/// The that holds the serialized object data about the exception being thrown.
/// The that contains contextual information about the source or destination.
/// The parameter is null.
/// The class name is null or is zero (0).
protected AuthenticationFailedException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
: base(info, context)
{
}
#endif
}
}