Java tutorial
/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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. */ package org.apache.hadoop.security.token; import com.google.common.collect.Maps; import com.google.protobuf.ByteString; import com.google.common.primitives.Bytes; import org.apache.commons.codec.binary.Base64; import org.apache.hadoop.HadoopIllegalArgumentException; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.classification.InterfaceStability; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.io.*; import org.apache.hadoop.security.proto.SecurityProtos.TokenProto; import org.apache.hadoop.util.ReflectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; import java.util.Arrays; import java.util.Map; import java.util.ServiceLoader; import java.util.UUID; /** * The client-side form of the token. */ @InterfaceAudience.Public @InterfaceStability.Evolving public class Token<T extends TokenIdentifier> implements Writable { public static final Logger LOG = LoggerFactory.getLogger(Token.class); private static Map<Text, Class<? extends TokenIdentifier>> tokenKindMap; private byte[] identifier; private byte[] password; private Text kind; private Text service; private TokenRenewer renewer; /** * Construct a token given a token identifier and a secret manager for the * type of the token identifier. * @param id the token identifier * @param mgr the secret manager */ public Token(T id, SecretManager<T> mgr) { password = mgr.createPassword(id); identifier = id.getBytes(); kind = id.getKind(); service = new Text(); } /** * Construct a token from the components. * @param identifier the token identifier * @param password the token's password * @param kind the kind of token * @param service the service for this token */ public Token(byte[] identifier, byte[] password, Text kind, Text service) { this.identifier = (identifier == null) ? new byte[0] : identifier; this.password = (password == null) ? new byte[0] : password; this.kind = (kind == null) ? new Text() : kind; this.service = (service == null) ? new Text() : service; } /** * Default constructor. */ public Token() { identifier = new byte[0]; password = new byte[0]; kind = new Text(); service = new Text(); } /** * Clone a token. * @param other the token to clone */ public Token(Token<T> other) { this.identifier = other.identifier.clone(); this.password = other.password.clone(); this.kind = new Text(other.kind); this.service = new Text(other.service); } public Token<T> copyToken() { return new Token<T>(this); } /** * Construct a Token from a TokenProto. * @param tokenPB the TokenProto object */ public Token(TokenProto tokenPB) { this.identifier = tokenPB.getIdentifier().toByteArray(); this.password = tokenPB.getPassword().toByteArray(); this.kind = new Text(tokenPB.getKindBytes().toByteArray()); this.service = new Text(tokenPB.getServiceBytes().toByteArray()); } /** * Construct a TokenProto from this Token instance. * @return a new TokenProto object holding copies of data in this instance */ public TokenProto toTokenProto() { return TokenProto.newBuilder().setIdentifier(ByteString.copyFrom(this.getIdentifier())) .setPassword(ByteString.copyFrom(this.getPassword())) .setKindBytes(ByteString.copyFrom(this.getKind().getBytes(), 0, this.getKind().getLength())) .setServiceBytes( ByteString.copyFrom(this.getService().getBytes(), 0, this.getService().getLength())) .build(); } /** * Get the token identifier's byte representation. * @return the token identifier's byte representation */ public byte[] getIdentifier() { return identifier; } private static Class<? extends TokenIdentifier> getClassForIdentifier(Text kind) { Class<? extends TokenIdentifier> cls = null; synchronized (Token.class) { if (tokenKindMap == null) { tokenKindMap = Maps.newHashMap(); for (TokenIdentifier id : ServiceLoader.load(TokenIdentifier.class)) { tokenKindMap.put(id.getKind(), id.getClass()); } } cls = tokenKindMap.get(kind); } if (cls == null) { LOG.debug("Cannot find class for token kind " + kind); return null; } return cls; } /** * Get the token identifier object, or null if it could not be constructed * (because the class could not be loaded, for example). * @return the token identifier, or null * @throws IOException */ @SuppressWarnings("unchecked") public T decodeIdentifier() throws IOException { Class<? extends TokenIdentifier> cls = getClassForIdentifier(getKind()); if (cls == null) { return null; } TokenIdentifier tokenIdentifier = ReflectionUtils.newInstance(cls, null); ByteArrayInputStream buf = new ByteArrayInputStream(identifier); DataInputStream in = new DataInputStream(buf); tokenIdentifier.readFields(in); in.close(); return (T) tokenIdentifier; } /** * Get the token password/secret. * @return the token password/secret */ public byte[] getPassword() { return password; } /** * Get the token kind. * @return the kind of the token */ public synchronized Text getKind() { return kind; } /** * Set the token kind. This is only intended to be used by services that * wrap another service's token. * @param newKind */ @InterfaceAudience.Private public synchronized void setKind(Text newKind) { kind = newKind; renewer = null; } /** * Get the service on which the token is supposed to be used. * @return the service name */ public Text getService() { return service; } /** * Set the service on which the token is supposed to be used. * @param newService the service name */ public void setService(Text newService) { service = newService; } /** * Whether this is a private token. * @return false always for non-private tokens */ public boolean isPrivate() { return false; } /** * Whether this is a private clone of a public token. * @param thePublicService the public service name * @return false always for non-private tokens */ public boolean isPrivateCloneOf(Text thePublicService) { return false; } /** * Create a private clone of a public token. * @param newService the new service name * @return a private token */ public Token<T> privateClone(Text newService) { return new PrivateToken<>(this, newService); } /** * Indicates whether the token is a clone. Used by HA failover proxy * to indicate a token should not be visible to the user via * UGI.getCredentials() */ static class PrivateToken<T extends TokenIdentifier> extends Token<T> { final private Text publicService; PrivateToken(Token<T> publicToken, Text newService) { super(publicToken.identifier, publicToken.password, publicToken.kind, newService); assert !publicToken.isPrivate(); publicService = publicToken.service; if (LOG.isDebugEnabled()) { LOG.debug("Cloned private token " + this + " from " + publicToken); } } /** * Whether this is a private token. * @return true always for private tokens */ @Override public boolean isPrivate() { return true; } /** * Whether this is a private clone of a public token. * @param thePublicService the public service name * @return true when the public service is the same as specified */ @Override public boolean isPrivateCloneOf(Text thePublicService) { return publicService.equals(thePublicService); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } if (!super.equals(o)) { return false; } PrivateToken<?> that = (PrivateToken<?>) o; return publicService.equals(that.publicService); } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + publicService.hashCode(); return result; } } @Override public void readFields(DataInput in) throws IOException { int len = WritableUtils.readVInt(in); if (identifier == null || identifier.length != len) { identifier = new byte[len]; } in.readFully(identifier); len = WritableUtils.readVInt(in); if (password == null || password.length != len) { password = new byte[len]; } in.readFully(password); kind.readFields(in); service.readFields(in); } @Override public void write(DataOutput out) throws IOException { WritableUtils.writeVInt(out, identifier.length); out.write(identifier); WritableUtils.writeVInt(out, password.length); out.write(password); kind.write(out); service.write(out); } /** * Generate a string with the url-quoted base64 encoded serialized form * of the Writable. * @param obj the object to serialize * @return the encoded string * @throws IOException */ private static String encodeWritable(Writable obj) throws IOException { DataOutputBuffer buf = new DataOutputBuffer(); obj.write(buf); Base64 encoder = new Base64(0, null, true); byte[] raw = new byte[buf.getLength()]; System.arraycopy(buf.getData(), 0, raw, 0, buf.getLength()); return encoder.encodeToString(raw); } /** * Modify the writable to the value from the newValue. * @param obj the object to read into * @param newValue the string with the url-safe base64 encoded bytes * @throws IOException */ private static void decodeWritable(Writable obj, String newValue) throws IOException { if (newValue == null) { throw new HadoopIllegalArgumentException("Invalid argument, newValue is null"); } Base64 decoder = new Base64(0, null, true); DataInputBuffer buf = new DataInputBuffer(); byte[] decoded = decoder.decode(newValue); buf.reset(decoded, decoded.length); obj.readFields(buf); } /** * Encode this token as a url safe string. * @return the encoded string * @throws IOException */ public String encodeToUrlString() throws IOException { return encodeWritable(this); } /** * Decode the given url safe string into this token. * @param newValue the encoded string * @throws IOException */ public void decodeFromUrlString(String newValue) throws IOException { decodeWritable(this, newValue); } @SuppressWarnings("unchecked") @Override public boolean equals(Object right) { if (this == right) { return true; } else if (right == null || getClass() != right.getClass()) { return false; } else { Token<T> r = (Token<T>) right; return Arrays.equals(identifier, r.identifier) && Arrays.equals(password, r.password) && kind.equals(r.kind) && service.equals(r.service); } } @Override public int hashCode() { return WritableComparator.hashBytes(identifier, identifier.length); } private static void addBinaryBuffer(StringBuilder buffer, byte[] bytes) { for (int idx = 0; idx < bytes.length; idx++) { // if not the first, put a blank separator in if (idx != 0) { buffer.append(' '); } String num = Integer.toHexString(0xff & bytes[idx]); // if it is only one digit, add a leading 0. if (num.length() < 2) { buffer.append('0'); } buffer.append(num); } } private void identifierToString(StringBuilder buffer) { T id = null; try { id = decodeIdentifier(); } catch (IOException e) { // handle in the finally block } finally { if (id != null) { buffer.append("(").append(id).append(")"); } else { addBinaryBuffer(buffer, identifier); } } } @Override public String toString() { StringBuilder buffer = new StringBuilder(); buffer.append("Kind: "); buffer.append(kind.toString()); buffer.append(", Service: "); buffer.append(service.toString()); buffer.append(", Ident: "); identifierToString(buffer); return buffer.toString(); } public String buildCacheKey() { return UUID.nameUUIDFromBytes(Bytes.concat(kind.getBytes(), identifier, password)).toString(); } private static ServiceLoader<TokenRenewer> renewers = ServiceLoader.load(TokenRenewer.class); private synchronized TokenRenewer getRenewer() throws IOException { if (renewer != null) { return renewer; } renewer = TRIVIAL_RENEWER; synchronized (renewers) { for (TokenRenewer canidate : renewers) { if (canidate.handleKind(this.kind)) { renewer = canidate; return renewer; } } } LOG.warn("No TokenRenewer defined for token kind " + this.kind); return renewer; } /** * Is this token managed so that it can be renewed or cancelled? * @return true, if it can be renewed and cancelled. */ public boolean isManaged() throws IOException { return getRenewer().isManaged(this); } /** * Renew this delegation token. * @return the new expiration time * @throws IOException * @throws InterruptedException */ public long renew(Configuration conf) throws IOException, InterruptedException { return getRenewer().renew(this, conf); } /** * Cancel this delegation token. * @throws IOException * @throws InterruptedException */ public void cancel(Configuration conf) throws IOException, InterruptedException { getRenewer().cancel(this, conf); } /** * A trivial renewer for token kinds that aren't managed. Sub-classes need * to implement getKind for their token kind. */ @InterfaceAudience.Public @InterfaceStability.Evolving public static class TrivialRenewer extends TokenRenewer { // define the kind for this renewer protected Text getKind() { return null; } @Override public boolean handleKind(Text kind) { return kind.equals(getKind()); } @Override public boolean isManaged(Token<?> token) { return false; } @Override public long renew(Token<?> token, Configuration conf) { throw new UnsupportedOperationException( "Token renewal is not supported " + " for " + token.kind + " tokens"); } @Override public void cancel(Token<?> token, Configuration conf) throws IOException, InterruptedException { throw new UnsupportedOperationException( "Token cancel is not supported " + " for " + token.kind + " tokens"); } } private static final TokenRenewer TRIVIAL_RENEWER = new TrivialRenewer(); }