/* * ***************************************************************************** * Cloud Foundry * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. * This product is licensed to you under the Apache License, Version 2.0 (the "License"). * You may not use this product except in compliance with the License. * * This product includes a number of subcomponents with * separate copyright notices and license terms. Your use of these * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. * ***************************************************************************** */ package org.cloudfoundry.identity.uaa.util; import org.apache.commons.codec.binary.Base64; import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.jwt.crypto.sign.Signer; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.web.util.UriComponentsBuilder; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; import java.util.stream.Collectors; import static java.util.Collections.emptySet; import static java.util.Optional.ofNullable; import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.CID; import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.GRANT_TYPE; import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.SUB; import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.USER_ID; import static org.springframework.util.StringUtils.hasText; public final class UaaTokenUtils { public static final Pattern jwtPattern = Pattern.compile("[a-zA-Z0-9_\\-\\\\=]*\\.[a-zA-Z0-9_\\-\\\\=]*\\.[a-zA-Z0-9_\\-\\\\=]*"); private UaaTokenUtils() { } public static String getRevocationHash(List<String> salts) { String result = ""; for (String s : salts) { byte[] hashable = (result+ "###" + s).getBytes(); result = Integer.toHexString(murmurhash3x8632(hashable, 0, hashable.length, 0xF0F0)); } return result; } /** * This code is public domain. * * The MurmurHash3 algorithm was created by Austin Appleby and put into the public domain. * @see <a href="http://code.google.com/p/smhasher">http://code.google.com/p/smhasher</a> * @see <a href="https://github.com/yonik/java_util/blob/master/src/util/hash/MurmurHash3.java">https://github.com/yonik/java_util/blob/master/src/util/hash/MurmurHash3.java</a> */ public static int murmurhash3x8632(byte[] data, int offset, int len, int seed) { int c1 = 0xcc9e2d51; int c2 = 0x1b873593; int h1 = seed; int roundedEnd = offset + (len & 0xfffffffc); // round down to 4 byte block for (int i = offset; i < roundedEnd; i += 4) { // little endian load order int k1 = (data[i] & 0xff) | ((data[i + 1] & 0xff) << 8) | ((data[i + 2] & 0xff) << 16) | (data[i + 3] << 24); k1 *= c1; k1 = (k1 << 15) | (k1 >>> 17); // ROTL32(k1,15); k1 *= c2; h1 ^= k1; h1 = (h1 << 13) | (h1 >>> 19); // ROTL32(h1,13); h1 = h1 * 5 + 0xe6546b64; } // tail int k1 = 0; switch(len & 0x03) { case 3: k1 = (data[roundedEnd + 2] & 0xff) << 16; // fallthrough case 2: k1 |= (data[roundedEnd + 1] & 0xff) << 8; // fallthrough case 1: k1 |= data[roundedEnd] & 0xff; k1 *= c1; k1 = (k1 << 15) | (k1 >>> 17); // ROTL32(k1,15); k1 *= c2; h1 ^= k1; default: } // finalization h1 ^= len; // fmix(h1); h1 ^= h1 >>> 16; h1 *= 0x85ebca6b; h1 ^= h1 >>> 13; h1 *= 0xc2b2ae35; h1 ^= h1 >>> 16; return h1; } public static Set<String> retainAutoApprovedScopes(Collection<String> requestedScopes, Set<String> autoApprovedScopes) { HashSet<String> result = new HashSet<>(); if(autoApprovedScopes == null){ return result; } if (autoApprovedScopes.contains("true")) { result.addAll(requestedScopes); return result; } Set<Pattern> autoApprovedScopePatterns = UaaStringUtils.constructWildcards(autoApprovedScopes); // Don't want to approve more than what's requested for (String scope : requestedScopes) { if (UaaStringUtils.matches(autoApprovedScopePatterns, scope)) { result.add(scope); } } return result; } public static boolean isUserToken(Map<String, Object> claims) { if (claims.get(GRANT_TYPE)!=null) { return !"client_credentials".equals(claims.get(GRANT_TYPE)); } if (claims.get(SUB)!=null) { if (claims.get(SUB).equals(claims.get(USER_ID))) { return true; } else if (claims.get(SUB).equals(claims.get(CID))) { return false; } } //err on the side of caution return true; } public static String getRevocableTokenSignature(ClientDetails client, String clientSecret, UaaUser user) { String[] salts = new String[] { client.getClientId(), clientSecret, (String)client.getAdditionalInformation().get(ClientConstants.TOKEN_SALT), user == null ? null : user.getId(), user == null ? null : user.getPassword(), user == null ? null : user.getSalt(), user == null ? null : user.getEmail(), user == null ? null : user.getUsername(), }; List<String> saltlist = new LinkedList<>(); for (String s : salts) { if (s!=null) { saltlist.add(s); } } return getRevocationHash(saltlist); } public static String constructToken(Map<String, Object> header, Map<String, Object> claims, Signer signer) { byte[] headerJson = header == null ? new byte[0] : JsonUtils.writeValueAsBytes(header); byte[] claimsJson = claims == null ? new byte[0] : JsonUtils.writeValueAsBytes(claims); String headerBase64 = Base64.encodeBase64URLSafeString(headerJson); String claimsBase64 = Base64.encodeBase64URLSafeString(claimsJson); String headerAndClaims = headerBase64 + "." + claimsBase64; byte[] signature = signer.sign(headerAndClaims.getBytes()); String signatureBase64 = Base64.encodeBase64URLSafeString(signature); return headerAndClaims + "." + signatureBase64; } public static boolean isJwtToken(String token) { return jwtPattern.matcher(token).matches(); } public static String constructTokenEndpointUrl(String issuer) throws URISyntaxException { try { new URL(issuer); } catch (MalformedURLException x) { throw new URISyntaxException(issuer, x.getMessage()); } URI uri = new URI(issuer); String hostToUse = uri.getHost(); if (hasText(IdentityZoneHolder.get().getSubdomain())) { hostToUse = IdentityZoneHolder.get().getSubdomain() + "." + hostToUse; } return UriComponentsBuilder.fromUriString(issuer).host(hostToUse).pathSegment("oauth/token").build().toUriString(); } public static boolean hasRequiredUserAuthorities(Collection<String> requiredGroups, Collection<? extends GrantedAuthority> userGroups) { return hasRequiredUserGroups(requiredGroups, ofNullable(userGroups).orElse(emptySet()) .stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.toList()) ); } public static boolean hasRequiredUserGroups(Collection<String> requiredGroups, Collection<String> userGroups) { return ofNullable(userGroups).orElse(emptySet()) .containsAll(ofNullable(requiredGroups).orElse(emptySet())); } }