/* Copyright 2010-present MongoDB Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
using MongoDB.Driver.Core.Authentication;
using MongoDB.Shared;
namespace MongoDB.Driver
{
///
/// Credential to access a MongoDB database.
///
#if NET452
[Serializable]
#endif
public class MongoCredential : IEquatable
{
// private fields
private readonly MongoIdentityEvidence _evidence;
private readonly MongoIdentity _identity;
private readonly string _mechanism;
private readonly Dictionary _mechanismProperties;
// constructors
///
/// Initializes a new instance of the class.
///
/// Mechanism to authenticate with.
/// In .NET Standard, authenticating via SCRAM-SHA-256 may not work with non-ASCII passwords because SaslPrep is
/// not fully implemented due to the lack of a string normalization function in .NET Standard 1.5.
/// Normalizing the password into Unicode Normalization Form KC beforehand MAY help.
/// SCRAM-SHA-1 is the recommended alternative for now.
/// The identity.
/// The evidence.
public MongoCredential(string mechanism, MongoIdentity identity, MongoIdentityEvidence evidence)
{
if (identity == null)
{
throw new ArgumentNullException("identity");
}
if (evidence == null)
{
throw new ArgumentNullException("evidence");
}
_mechanism = mechanism;
_identity = identity;
_evidence = evidence;
_mechanismProperties = new Dictionary();
}
// public properties
///
/// Gets the evidence.
///
public MongoIdentityEvidence Evidence
{
get { return _evidence; }
}
///
/// Gets the identity.
///
public MongoIdentity Identity
{
get { return _identity; }
}
///
/// Gets the mechanism to authenticate with.
/// In .NET Standard, authenticating via SCRAM-SHA-256 may not work with non-ASCII passwords because SaslPrep is
/// not fully implemented due to the lack of a string normalization function in .NET Standard 1.5.
/// Normalizing the password into Unicode Normalization Form KC beforehand MAY help.
/// SCRAM-SHA-1 is the recommended alternative for now.
///
public string Mechanism
{
get { return _mechanism; }
}
///
/// Gets the password.
///
[Obsolete("Use Evidence instead.")]
public string Password
{
get
{
var passwordEvidence = _evidence as PasswordEvidence;
if (passwordEvidence != null)
{
return MongoUtils.ToInsecureString(passwordEvidence.SecurePassword);
}
return null;
}
}
///
/// Gets the source.
///
public string Source
{
get { return _identity.Source; }
}
///
/// Gets the username.
///
public string Username
{
get { return _identity.Username; }
}
// public operators
///
/// Compares two MongoCredentials.
///
/// The first MongoCredential.
/// The other MongoCredential.
/// True if the two MongoCredentials are equal (or both null).
public static bool operator ==(MongoCredential lhs, MongoCredential rhs)
{
return object.Equals(lhs, rhs);
}
///
/// Compares two MongoCredentials.
///
/// The first MongoCredential.
/// The other MongoCredential.
/// True if the two MongoCredentials are not equal (or one is null and the other is not).
public static bool operator !=(MongoCredential lhs, MongoCredential rhs)
{
return !(lhs == rhs);
}
// public static methods
///
/// Creates a default credential.
/// In .NET Standard, authenticating via SCRAM-SHA-256 may not work with non-ASCII passwords because SaslPrep is
/// not fully implemented due to the lack of a string normalization function in .NET Standard 1.5.
/// Normalizing the password into Unicode Normalization Form KC beforehand MAY help.
/// SCRAM-SHA-1 is the recommended alternative for now.
///
/// Name of the database.
/// The username.
/// The password.
/// A default credential.
public static MongoCredential CreateCredential(string databaseName, string username, string password)
{
return FromComponents(null,
databaseName,
username,
new PasswordEvidence(password));
}
///
/// Creates a default credential.
/// Less secure when used in conjunction with SCRAM-SHA-256, due to the need to store the password in a managed
/// string in order to SaslPrep it.
/// In .NET Standard, authenticating via SCRAM-SHA-256 may not work with non-ASCII passwords because SaslPrep is
/// not fully implemented due to the lack of a string normalization function in .NET Standard 1.5.
/// Normalizing the password into Unicode Normalization Form KC beforehand MAY help.
/// SCRAM-SHA-1 is the recommended alternative for now.
///
/// Name of the database.
/// The username.
/// The password.
/// A default credential.
public static MongoCredential CreateCredential(string databaseName, string username, SecureString password)
{
return FromComponents(null,
databaseName,
username,
new PasswordEvidence(password));
}
///
/// Creates a GSSAPI credential.
///
/// The username.
/// A credential for GSSAPI.
/// This overload is used primarily on linux.
public static MongoCredential CreateGssapiCredential(string username)
{
return FromComponents("GSSAPI",
"$external",
username,
new ExternalEvidence());
}
///
/// Creates a GSSAPI credential.
///
/// The username.
/// The password.
/// A credential for GSSAPI.
public static MongoCredential CreateGssapiCredential(string username, string password)
{
return FromComponents("GSSAPI",
"$external",
username,
new PasswordEvidence(password));
}
///
/// Creates a GSSAPI credential.
///
/// The username.
/// The password.
/// A credential for GSSAPI.
public static MongoCredential CreateGssapiCredential(string username, SecureString password)
{
return FromComponents("GSSAPI",
"$external",
username,
new PasswordEvidence(password));
}
///
/// Creates a credential used with MONGODB-CR.
///
/// Name of the database.
/// The username.
/// The password.
/// A credential for MONGODB-CR.
[Obsolete("MONGODB-CR was replaced by SCRAM-SHA-1 in MongoDB 3.0, and is now deprecated.")]
public static MongoCredential CreateMongoCRCredential(string databaseName, string username, string password)
{
return FromComponents("MONGODB-CR",
databaseName,
username,
new PasswordEvidence(password));
}
///
/// Creates a credential used with MONGODB-CR.
///
/// Name of the database.
/// The username.
/// The password.
/// A credential for MONGODB-CR.
[Obsolete("MONGODB-CR was replaced by SCRAM-SHA-1 in MongoDB 3.0, and is now deprecated.")]
public static MongoCredential CreateMongoCRCredential(string databaseName, string username, SecureString password)
{
return FromComponents("MONGODB-CR",
databaseName,
username,
new PasswordEvidence(password));
}
///
/// Creates a credential used with MONGODB-X509.
///
/// The username.
/// A credential for MONGODB-X509.
public static MongoCredential CreateMongoX509Credential(string username)
{
return FromComponents("MONGODB-X509",
"$external",
username,
new ExternalEvidence());
}
///
/// Creates a PLAIN credential.
///
/// Name of the database.
/// The username.
/// The password.
/// A credential for PLAIN.
public static MongoCredential CreatePlainCredential(string databaseName, string username, string password)
{
return FromComponents("PLAIN",
databaseName,
username,
new PasswordEvidence(password));
}
///
/// Creates a PLAIN credential.
///
/// Name of the database.
/// The username.
/// The password.
/// A credential for PLAIN.
public static MongoCredential CreatePlainCredential(string databaseName, string username, SecureString password)
{
return FromComponents("PLAIN",
databaseName,
username,
new PasswordEvidence(password));
}
// public methods
///
/// Gets the mechanism property.
///
/// The type of the mechanism property.
/// The key.
/// The default value.
/// The mechanism property if one was set; otherwise the default value.
public T GetMechanismProperty(string key, T defaultValue)
{
object value;
if (_mechanismProperties.TryGetValue(key, out value))
{
return (T)value;
}
return defaultValue;
}
///
/// Compares this MongoCredential to another MongoCredential.
///
/// The other credential.
/// True if the two credentials are equal.
public bool Equals(MongoCredential rhs)
{
if (object.ReferenceEquals(rhs, null) || GetType() != rhs.GetType()) { return false; }
return _identity == rhs._identity &&
_evidence == rhs._evidence &&
_mechanism == rhs._mechanism &&
_mechanismProperties.OrderBy(x => x.Key).SequenceEqual(rhs._mechanismProperties.OrderBy(x => x.Key));
}
///
/// Compares this MongoCredential to another MongoCredential.
///
/// The other credential.
/// True if the two credentials are equal.
public override bool Equals(object obj)
{
return Equals(obj as MongoCredential); // works even if obj is null or of a different type
}
///
/// Gets the hashcode for the credential.
///
/// The hashcode.
public override int GetHashCode()
{
// see Effective Java by Joshua Bloch
return new Hasher()
.Hash(_identity)
.Hash(_evidence)
.Hash(_mechanism)
.HashStructElements(_mechanismProperties)
.GetHashCode();
}
///
/// Returns a string representation of the credential.
///
/// A string representation of the credential.
public override string ToString()
{
return string.Format("{0}@{1}", _identity.Username, _identity.Source);
}
///
/// Creates a new MongoCredential with the specified mechanism property.
///
/// The key.
/// The value.
/// A new MongoCredential with the specified mechanism property.
public MongoCredential WithMechanismProperty(string key, object value)
{
var copy = new MongoCredential(_mechanism, _identity, _evidence);
foreach (var pair in _mechanismProperties)
{
copy._mechanismProperties.Add(pair.Key, pair.Value);
}
copy._mechanismProperties[key] = value; // overwrite if it's already set
return copy;
}
// internal methods
internal IAuthenticator ToAuthenticator()
{
var passwordEvidence = _evidence as PasswordEvidence;
if (passwordEvidence != null)
{
var insecurePassword = MongoUtils.ToInsecureString(passwordEvidence.SecurePassword);
var credential = new UsernamePasswordCredential(
_identity.Source,
_identity.Username,
insecurePassword);
if (_mechanism == null)
{
return new DefaultAuthenticator(credential);
}
#pragma warning disable 618
else if (_mechanism == MongoDBCRAuthenticator.MechanismName)
{
return new MongoDBCRAuthenticator(credential);
#pragma warning restore 618
}
else if (_mechanism == ScramSha1Authenticator.MechanismName)
{
return new ScramSha1Authenticator(credential);
}
else if (_mechanism == ScramSha256Authenticator.MechanismName)
{
return new ScramSha256Authenticator(credential);
}
else if (_mechanism == PlainAuthenticator.MechanismName)
{
return new PlainAuthenticator(credential);
}
else if (_mechanism == GssapiAuthenticator.MechanismName)
{
return new GssapiAuthenticator(
credential,
_mechanismProperties.Select(x => new KeyValuePair(x.Key, x.Value.ToString())));
}
}
else if (_identity.Source == "$external" && _evidence is ExternalEvidence)
{
if (_mechanism == MongoDBX509Authenticator.MechanismName)
{
return new MongoDBX509Authenticator(_identity.Username);
}
else if (_mechanism == GssapiAuthenticator.MechanismName)
{
return new GssapiAuthenticator(
_identity.Username,
_mechanismProperties.Select(x => new KeyValuePair(x.Key, x.Value.ToString())));
}
}
throw new NotSupportedException("Unable to create an authenticator.");
}
// internal static methods
internal static MongoCredential FromComponents(string mechanism, string source, string username, string password)
{
var evidence = password == null ? (MongoIdentityEvidence)new ExternalEvidence() : new PasswordEvidence(password);
return FromComponents(mechanism, source, username, evidence);
}
// private methods
private void ValidatePassword(string password)
{
if (password == null)
{
throw new ArgumentNullException("password");
}
if (password.Any(c => (int)c >= 128))
{
throw new ArgumentException("Password must contain only ASCII characters.");
}
}
// private static methods
private static MongoCredential FromComponents(string mechanism, string source, string username, MongoIdentityEvidence evidence)
{
var defaultedMechanism = (mechanism ?? "DEFAULT").Trim().ToUpperInvariant();
switch (defaultedMechanism)
{
case "DEFAULT":
case "MONGODB-CR":
case "SCRAM-SHA-1":
case "SCRAM-SHA-256":
// it is allowed for a password to be an empty string, but not a username
source = source ?? "admin";
if (evidence == null || !(evidence is PasswordEvidence))
{
var message = string.Format("A {0} credential must have a password.", defaultedMechanism);
throw new ArgumentException(message);
}
return new MongoCredential(
mechanism,
new MongoInternalIdentity(source, username),
evidence);
case "MONGODB-X509":
// always $external for X509.
source = "$external";
if (evidence == null || !(evidence is ExternalEvidence))
{
throw new ArgumentException("A MONGODB-X509 does not support a password.");
}
return new MongoCredential(
mechanism,
new MongoX509Identity(username),
evidence);
case "GSSAPI":
// always $external for GSSAPI.
source = "$external";
return new MongoCredential(
"GSSAPI",
new MongoExternalIdentity(source, username),
evidence);
case "PLAIN":
source = source ?? "admin";
if (evidence == null || !(evidence is PasswordEvidence))
{
throw new ArgumentException("A PLAIN credential must have a password.");
}
MongoIdentity identity;
if (source == "$external")
{
identity = new MongoExternalIdentity(source, username);
}
else
{
identity = new MongoInternalIdentity(source, username);
}
return new MongoCredential(
mechanism,
identity,
evidence);
default:
throw new NotSupportedException(string.Format("Unsupported MongoAuthenticationMechanism {0}.", mechanism));
}
}
}
}