/*
* Copyright 2015 Google, Inc. 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.
* 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 com.examples.abelanav2.grpc;
import com.google.common.base.Preconditions;
import com.google.gson.JsonObject;
import com.google.identitytoolkit.GitkitClient;
import com.google.identitytoolkit.GitkitClientException;
import com.google.identitytoolkit.GitkitUser;
import com.examples.abelanav2.BackendConstants;
import net.oauth.jsontoken.Checker;
import net.oauth.jsontoken.JsonToken;
import net.oauth.jsontoken.JsonTokenParser;
import net.oauth.jsontoken.crypto.HmacSHA256Signer;
import net.oauth.jsontoken.crypto.HmacSHA256Verifier;
import net.oauth.jsontoken.crypto.SignatureAlgorithm;
import net.oauth.jsontoken.crypto.Verifier;
import net.oauth.jsontoken.discovery.VerifierProvider;
import net.oauth.jsontoken.discovery.VerifierProviders;
import java.io.FileNotFoundException;
import java.security.InvalidKeyException;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
/**
* Provides different useful method both to check the validity of Gitkit tokens
* and to generate and verify the application own's tokens.
*/
public final class AuthUtils {
/**
* The verifier providers for the token signing.
*/
private static VerifierProviders verifierProviders = null;
static {
try {
final Verifier hmacVerifier =
new HmacSHA256Verifier(BackendConstants.SIGNING_KEY.getBytes());
verifierProviders = new VerifierProviders();
verifierProviders.setVerifierProvider(SignatureAlgorithm.HS256,
new VerifierProvider() {
@Override
public List<Verifier> findVerifier(final String signerId,
final String keyId) {
List<Verifier> list = new ArrayList<>();
list.add(hmacVerifier);
return list;
}
});
} catch (InvalidKeyException e) {
e.printStackTrace();
}
}
/**
* Constructor.
*/
private AuthUtils() { }
/**
* Returns a signed JWT token containing the userId.
* @param userId the userId to include in the token payload.
* @return a signed JWT token.
* @throws InvalidKeyException if the signing key cannot be retrieved.
* @throws SignatureException if the signature failed.
*/
public static String getJwt(final String userId) throws InvalidKeyException, SignatureException {
JsonToken token;
token = createToken(userId);
return token.serializeAndSign();
}
/**
* Creates and returns the token to be signed.
* @param userId the userId to include in the token payload.
* @return a token to be signed.
* @throws InvalidKeyException if the signing key cannot be retrieved.
*/
private static JsonToken createToken(final String userId) throws InvalidKeyException {
// Current time and signing algorithm
Calendar cal = Calendar.getInstance();
HmacSHA256Signer signer = new HmacSHA256Signer(BackendConstants.TOKEN_ISSUER, null,
BackendConstants.SIGNING_KEY.getBytes());
// Configure JSON token with signer and SystemClock
JsonToken token = new JsonToken(signer);
token.setAudience(BackendConstants.TOKEN_ISSUER);
token.setParam("typ", "abelana/auth/v1");
token.setIssuedAt(new org.joda.time.Instant(cal.getTimeInMillis()));
token.setExpiration(new org.joda.time.Instant(cal.getTimeInMillis()
+ BackendConstants.JWT_EXPIRATION_DURATION));
//Configure user object, which contains information on the user
JsonObject user = new JsonObject();
user.addProperty("user_id", userId);
JsonObject payload = token.getPayloadAsJsonObject();
payload.add("user", user);
return token;
}
/**
* Deserializes the JWT signed token.
* @param jwt the signed token.
* @return the deserialized token.
* @throws Exception if cannot deserialize
*/
public static JsonToken deserialize(final String jwt) throws Exception {
JsonTokenParser parser = new JsonTokenParser(verifierProviders,
new AbelanaTokenAudienceChecker(BackendConstants.TOKEN_ISSUER));
return parser.deserialize(jwt);
}
/**
* Checks the signature and deserializes the JWT signed token.
* @param jwt the signed token.
* @return the deserialized token
* @throws Exception if cannot verify signature or cannot deserialize
*/
public static JsonToken verifyAndDeserialize(final String jwt) throws Exception {
JsonTokenParser parser = new JsonTokenParser(verifierProviders,
new AbelanaTokenAudienceChecker(BackendConstants.TOKEN_ISSUER));
return parser.verifyAndDeserialize(jwt);
}
/**
* Verifies the signature and audience of the GitkitToken.
* @param gitkitToken the GitkitToken to check.
* @return the GitkitUser associated to the GitkitToken.
* @throws GitkitClientException if the GitkitClient cannot validate the
* token.
* @throws FileNotFoundException if the service account key cannot be found.
*/
public static GitkitUser verifyGitkitToken(final String gitkitToken)
throws GitkitClientException, FileNotFoundException {
// Initializes Gitkit client instance
GitkitClient gitkitClient = GitkitClient.newBuilder()
.setGoogleClientId(BackendConstants.GOOGLE_CLIENT_ID)
.setServiceAccountEmail(BackendConstants.GOOGLE_SERVICE_ACCOUNT)
.setKeyStream(Thread.currentThread().getContextClassLoader()
.getResourceAsStream(BackendConstants.GOOGLE_SERVICE_ACCOUNT_KEY_FILEPATH))
.setWidgetUrl("/gitkit.jsp").setCookieName("gtoken").build();
// Verifies a GitkitToken
return gitkitClient.validateToken(gitkitToken);
}
/**
* Checks if the current user is signed in.
* @return boolean indicating if the user is signed in.
*/
public static boolean isSignedIn() {
String userIdToken = AuthToken.get();
try {
verifyAndDeserialize(userIdToken);
} catch (Exception e) {
return false;
}
return true;
}
/**
* Returns the user id.
* @return String the user id.
*/
public static String getUserId() {
String userIdToken = AuthToken.get();
try {
JsonToken token = deserialize(userIdToken);
JsonObject payload = token.getPayloadAsJsonObject();
JsonObject user = payload.get("user").getAsJsonObject();
return user.get("user_id").getAsString();
} catch (Exception e) {
return null;
}
}
/**
* Audience checker for signed JWT tokens.
*/
public static class AbelanaTokenAudienceChecker implements Checker {
/**
* URI that the client is accessing, as seen by the server.
*/
private final String serverUri;
/**
* Public constructor.
* @param uri the URI against which the signed JWT token was exercised.
*/
public AbelanaTokenAudienceChecker(final String uri) {
this.serverUri = uri;
}
/** Cheks the payload.
* @see Checker#check(JsonObject)
*/
@Override
public final void check(final JsonObject payload) throws SignatureException {
checkUri(serverUri, Preconditions.checkNotNull(payload.get(JsonToken.AUDIENCE).getAsString(),
"Audience cannot be null!"));
}
/**
* Checks the audience field.
* @param ourUriString the authorized audience.
* @param tokenUriString the audience in the token.
* @throws SignatureException if the audience is not valid.
*/
private void checkUri(final String ourUriString, final String tokenUriString)
throws SignatureException {
if (!tokenUriString.equalsIgnoreCase(ourUriString)) {
throw new SignatureException("Wrong audience URI");
}
}
}
}