com.microsoft.alm.secret.Token Maven / Gradle / Ivy
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root.
package com.microsoft.alm.secret;
import com.microsoft.alm.helpers.Debug;
import com.microsoft.alm.helpers.Guid;
import com.microsoft.alm.helpers.NotImplementedException;
import com.microsoft.alm.helpers.StringHelper;
import com.microsoft.alm.helpers.XmlHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import javax.xml.bind.DatatypeConverter;
import java.nio.ByteBuffer;
import java.util.EnumSet;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
/**
* A security token, usually acquired by some authentication and identity services.
*/
public class Token extends Secret {
private static final Logger logger = LoggerFactory.getLogger(Token.class);
private static final int sizeofTokenType = 4;
private static final int sizeofGuid = 16;
public static boolean getFriendlyNameFromType(final TokenType type, final AtomicReference name) {
// PORT NOTE: Java doesn't have the concept of out-of-range enums
name.set(null);
name.set(type.getDescription() == null
? type.toString()
: type.getDescription());
return name.get() != null;
}
public static boolean getTypeFromFriendlyName(final String name, final AtomicReference type) {
Debug.Assert(!StringHelper.isNullOrWhiteSpace(name), "The name parameter is null or invalid");
type.set(TokenType.Unknown);
for (final TokenType value : EnumSet.allOf(TokenType.class)) {
type.set(value);
AtomicReference typename = new AtomicReference();
if (getFriendlyNameFromType(type.get(), typename)) {
if (name.equalsIgnoreCase(typename.get()))
return true;
}
}
return false;
}
public Token(final String value, final TokenType type) {
Debug.Assert(!StringHelper.isNullOrWhiteSpace(value), "The value parameter is null or invalid");
// PORT NOTE: Java doesn't have the concept of out-of-range enums
this.Type = type;
this.Value = value;
}
public Token(final String value, final String typeName) {
Debug.Assert(!StringHelper.isNullOrWhiteSpace(value), "The value parameter is null or invalid");
Debug.Assert(!StringHelper.isNullOrWhiteSpace(typeName), "The typeName parameter is null or invalid");
AtomicReference type = new AtomicReference();
if (!getTypeFromFriendlyName(typeName, type)) {
throw new IllegalArgumentException("Unexpected token type '" + typeName + "' encountered");
}
this.Type = type.get();
this.Value = value;
}
// PORT NOTE: ADAL-specific constructor omitted
/**
* The type of the security token.
*/
public final TokenType Type;
/**
* The raw contents of the token.
*/
public final String Value;
UUID targetIdentity = Guid.Empty;
public static Token fromXml(final Node tokenNode) {
Token value;
String tokenValue = null;
TokenType tokenType = null;
UUID targetIdentity = Guid.Empty;
final NodeList propertyNodes = tokenNode.getChildNodes();
for (int v = 0; v < propertyNodes.getLength(); v++) {
final Node propertyNode = propertyNodes.item(v);
final String propertyName = propertyNode.getNodeName();
if ("Type".equals(propertyName)) {
tokenType = TokenType.valueOf(TokenType.class, XmlHelper.getText(propertyNode));
} else if ("Value".equals(propertyName)) {
tokenValue = XmlHelper.getText(propertyNode);
} else if ("targetIdentity".equals(propertyName)) {
targetIdentity = UUID.fromString(XmlHelper.getText(propertyNode));
}
}
value = new Token(tokenValue, tokenType);
value.setTargetIdentity(targetIdentity);
return value;
}
public Element toXml(final Document document) {
final Element valueNode = document.createElement("value");
final Element typeNode = document.createElement("Type");
final Text typeValue = document.createTextNode(this.Type.toString());
typeNode.appendChild(typeValue);
valueNode.appendChild(typeNode);
final Element tokenValueNode = document.createElement("Value");
final Text valueValue = document.createTextNode(this.Value);
tokenValueNode.appendChild(valueValue);
valueNode.appendChild(tokenValueNode);
if (!Guid.Empty.equals(this.getTargetIdentity())) {
final Element targetIdentityNode = document.createElement("targetIdentity");
final Text targetIdentityValue = document.createTextNode(this.getTargetIdentity().toString());
targetIdentityNode.appendChild(targetIdentityValue);
valueNode.appendChild(targetIdentityNode);
}
return valueNode;
}
/**
* @return The guid form Identity of the target
*/
public UUID getTargetIdentity() {
return targetIdentity;
}
public void setTargetIdentity(final UUID targetIdentity) {
this.targetIdentity = targetIdentity;
}
/**
* Compares an object to this {@link Token} for equality.
*
* @param obj The object to compare.
* @return True is equal; false otherwise.
*/
@Override
public boolean equals(final Object obj) {
return operatorEquals(this, obj instanceof Token ? ((Token) obj) : null);
}
// PORT NOTE: Java doesn't support a specific overload (as per IEquatable)
/**
* Gets a hash code based on the contents of the token.
*
* @return 32-bit hash code.
*/
@Override
public int hashCode() {
// PORT NOTE: Java doesn't have unchecked blocks; the default behaviour is apparently equivalent.
{
return Type.getValue() * Value.hashCode();
}
}
/**
* Converts the token to a human friendly string.
*
* @return Humanish name of the token.
*/
@Override
public String toString() {
final AtomicReference value = new AtomicReference();
if (getFriendlyNameFromType(Type, value))
return value.get();
else
return super.toString();
}
public void contributeHeader(final Map headers) {
// different types of tokens are packed differently
switch (Type) {
case Access:
final String prefix = "Bearer";
headers.put("Authorization", prefix + " " + Value);
break;
case Personal:
final byte[] authData = StringHelper.UTF8GetBytes("PersonalAccessToken:" + Value);
final String base64EncodedAuthData = DatatypeConverter.printBase64Binary(authData);
headers.put("Authorization", "Basic " + base64EncodedAuthData);
break;
case Federated:
throw new NotImplementedException(449222);
default:
final String template = "Tokens of type '%1$s' cannot be used for headers.";
final String message = String.format(template, Type);
throw new IllegalStateException(message);
}
}
static boolean deserialize(final byte[] bytes, final TokenType type, final AtomicReference tokenReference) {
Debug.Assert(bytes != null, "The bytes parameter is null");
Debug.Assert(bytes.length > 0, "The bytes parameter is too short");
Debug.Assert(type != null, "The type parameter is invalid");
tokenReference.set(null);
try {
final int preamble = sizeofTokenType + sizeofGuid;
if (bytes.length > preamble) {
TokenType readType;
UUID targetIdentity;
final ByteBuffer p = ByteBuffer.wrap(bytes); // PORT NOTE: ByteBuffer is closest to "fixed"
{
readType = TokenType.fromValue(Integer.reverseBytes(p.getInt()));
byte[] guidBytes = new byte[16];
p.get(guidBytes);
targetIdentity = Guid.fromBytes(guidBytes);
}
if (readType == type) {
final String value = StringHelper.UTF8GetString(bytes, preamble, bytes.length - preamble);
if (!StringHelper.isNullOrWhiteSpace(value)) {
tokenReference.set(new Token(value, type));
tokenReference.get().targetIdentity = targetIdentity;
}
}
}
// if value hasn't been set yet, fall back to old format decode
if (tokenReference.get() == null) {
final String value = StringHelper.UTF8GetString(bytes);
if (!StringHelper.isNullOrWhiteSpace(value)) {
tokenReference.set(new Token(value, type));
}
}
} catch (final Throwable throwable) {
logger.debug(" token deserialization error");
}
return tokenReference.get() != null;
}
static boolean serialize(final Token token, final AtomicReference byteReference) {
Debug.Assert(token != null, "The token parameter is null");
Debug.Assert(!StringHelper.isNullOrWhiteSpace(token.Value), "The token.Value is invalid");
byteReference.set(null);
try {
final byte[] utf8bytes = StringHelper.UTF8GetBytes(token.Value);
final ByteBuffer bytes = ByteBuffer.allocate(utf8bytes.length + sizeofTokenType + sizeofGuid);
// PORT NOTE: "fixed" block pointer arithmetic and casting avoided
{
bytes.putInt(Integer.reverseBytes(token.Type.getValue()));
bytes.put(Guid.toBytes(token.targetIdentity));
}
bytes.put(utf8bytes);
byteReference.set(bytes.array());
} catch (final Throwable t) {
logger.debug(" token serialization error");
}
return byteReference.get() != null;
}
public static void validate(final Token token) {
if (token == null)
throw new IllegalArgumentException("The `token` parameter is null or invalid.");
if (StringHelper.isNullOrWhiteSpace(token.Value))
throw new IllegalArgumentException("The value of the `token` cannot be null or empty.");
if (token.Value.length() > Credential.PASSWORD_MAX_LENGTH)
throw new IllegalArgumentException(String.format("The value of the `token` cannot be longer than %1$d " +
"characters.", Credential.PASSWORD_MAX_LENGTH));
}
/**
* Explicitly casts a personal access token token into a set of credentials
*
* @param token The {@link Token} to convert.
* @return A corresponding {@link Credential} instance.
* @throws IllegalArgumentException if the {@link Token#Type} is not {@link TokenType#Personal}.
*/
// PORT NOTE: Java doesn't have cast operator overloading
public static Credential toCredential(final Token token) {
if (token == null)
return null;
if (token.Type != TokenType.Personal)
throw new IllegalArgumentException("Cannot convert " + token.toString() + " to credentials");
return new Credential(token.toString(), token.Value);
}
/**
* Compares two tokens for equality.
*
* @param token1 Token to compare.
* @param token2 Token to compare.
* @return True if equal; false otherwise.
*/
public static boolean operatorEquals(final Token token1, final Token token2) {
if (token1 == token2)
return true;
if ((token1 == null) || (null == token2))
return false;
return token1.Type == token2.Type
&& token1.Value.equalsIgnoreCase(token2.Value);
}
/**
* Compares two tokens for inequality.
*
* @param token1 Token to compare.
* @param token2 Token to compare.
* @return False if equal; true otherwise.
*/
public static boolean operatorNotEquals(final Token token1, final Token token2) {
return !operatorEquals(token1, token2);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy