1 /* 2 * Copyright 2015, Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * 15 * * Neither the name of Google Inc. nor the names of its 16 * contributors may be used to endorse or promote products derived from 17 * this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 package com.google.auth.oauth2; 33 34 import com.google.api.client.json.GenericJson; 35 import com.google.api.client.json.JsonObjectParser; 36 import com.google.api.client.util.Preconditions; 37 import com.google.errorprone.annotations.CanIgnoreReturnValue; 38 import java.io.IOException; 39 import java.io.InputStream; 40 import java.nio.charset.StandardCharsets; 41 import java.util.Map; 42 43 /** 44 * An OAuth2 user authorization Client ID and associated information. 45 * 46 * <p>Corresponds to the information in the json file downloadable for a Client ID. 47 */ 48 public class ClientId { 49 50 private static final String FIELD_TYPE_INSTALLED = "installed"; 51 private static final String FIELD_TYPE_WEB = "web"; 52 private static final String FIELD_CLIENT_ID = "client_id"; 53 private static final String FIELD_CLIENT_SECRET = "client_secret"; 54 private static final String JSON_PARSE_ERROR = "Error parsing Client ID JSON: "; 55 56 private final String clientId; 57 private final String clientSecret; 58 59 /** 60 * Constructs a client ID from an explicit ID and secret. 61 * 62 * <p>Note: Direct use of this factory method in application code is not recommended to avoid 63 * having secrets or values that need to be updated in source code. 64 * 65 * @param clientId Text identifier of the Client ID. 66 * @param clientSecret Secret to associated with the Client ID. 67 * @return The ClientId instance. 68 */ of(String clientId, String clientSecret)69 public static ClientId of(String clientId, String clientSecret) { 70 return new ClientId(clientId, clientSecret); 71 } 72 73 /** 74 * Constructs a Client ID from JSON from a downloaded file. 75 * 76 * @param json the JSON from the downloaded file 77 * @return the ClientId instance based on the JSON 78 * @throws IOException the JSON could not be parsed 79 */ fromJson(Map<String, Object> json)80 public static ClientId fromJson(Map<String, Object> json) throws IOException { 81 Object rawDetail = null; 82 rawDetail = json.get(FIELD_TYPE_INSTALLED); 83 if (rawDetail == null) { 84 rawDetail = json.get(FIELD_TYPE_WEB); 85 } 86 if (rawDetail == null || !(rawDetail instanceof Map<?, ?>)) { 87 throw new IOException( 88 "Unable to parse Client ID JSON. Expecting top-level field '" 89 + FIELD_TYPE_WEB 90 + "' or '" 91 + FIELD_TYPE_INSTALLED 92 + "' of collection type"); 93 } 94 @SuppressWarnings("unchecked") 95 Map<String, Object> detail = (Map<String, Object>) rawDetail; 96 String clientId = OAuth2Utils.validateString(detail, FIELD_CLIENT_ID, JSON_PARSE_ERROR); 97 if (clientId == null || clientId.length() == 0) { 98 throw new IOException( 99 "Unable to parse ClientId. Field '" + FIELD_CLIENT_ID + "' is required."); 100 } 101 String clientSecret = 102 OAuth2Utils.validateOptionalString(detail, FIELD_CLIENT_SECRET, JSON_PARSE_ERROR); 103 return new ClientId(clientId, clientSecret); 104 } 105 106 /** 107 * Constructs a Client ID from JSON file stored as a resource. 108 * 109 * @param relativeClass a class in the same namespace as the resource 110 * @param resourceName the name of the resource 111 * @return the constructed ClientID instance based on the JSON in the resource 112 * @throws IOException The JSON could not be loaded or parsed. 113 */ fromResource(Class<?> relativeClass, String resourceName)114 public static ClientId fromResource(Class<?> relativeClass, String resourceName) 115 throws IOException { 116 InputStream stream = relativeClass.getResourceAsStream(resourceName); 117 return fromStream(stream); 118 } 119 120 /** 121 * Constructs a Client ID from JSON file stream. 122 * 123 * @param stream the downloaded JSON file 124 * @return the constructed ClientID instance based on the JSON in the stream 125 * @throws IOException the JSON could not be read or parsed 126 */ fromStream(InputStream stream)127 public static ClientId fromStream(InputStream stream) throws IOException { 128 Preconditions.checkNotNull(stream); 129 JsonObjectParser parser = new JsonObjectParser(OAuth2Utils.JSON_FACTORY); 130 GenericJson parsedJson = 131 parser.parseAndClose(stream, StandardCharsets.UTF_8, GenericJson.class); 132 return fromJson(parsedJson); 133 } 134 135 /** 136 * Constructs a client ID using an explicit ID and secret 137 * 138 * <p>Note: Direct use of this constructor in application code is not recommended to avoid having 139 * secrets or values that need to be updated in source code. 140 * 141 * @param clientId Text identifier of the Client ID. 142 * @param clientSecret Secret to associated with the Client ID. 143 */ ClientId(String clientId, String clientSecret)144 private ClientId(String clientId, String clientSecret) { 145 this.clientId = Preconditions.checkNotNull(clientId); 146 this.clientSecret = clientSecret; 147 } 148 149 /** 150 * Returns the text identifier of the Client ID. 151 * 152 * @return The text identifier of the Client ID. 153 */ getClientId()154 public final String getClientId() { 155 return clientId; 156 } 157 158 /** 159 * Returns the secret associated with the Client ID. 160 * 161 * @return The secret associated with the Client ID. 162 */ getClientSecret()163 public final String getClientSecret() { 164 return clientSecret; 165 } 166 newBuilder()167 public static Builder newBuilder() { 168 return new Builder(); 169 } 170 toBuilder()171 public Builder toBuilder() { 172 return new Builder(this); 173 } 174 175 public static class Builder { 176 177 private String clientId; 178 179 private String clientSecret; 180 Builder()181 protected Builder() {} 182 Builder(ClientId clientId)183 protected Builder(ClientId clientId) { 184 this.clientId = clientId.getClientId(); 185 this.clientSecret = clientId.getClientSecret(); 186 } 187 188 @CanIgnoreReturnValue setClientId(String clientId)189 public Builder setClientId(String clientId) { 190 this.clientId = clientId; 191 return this; 192 } 193 194 @CanIgnoreReturnValue setClientSecret(String clientSecret)195 public Builder setClientSecret(String clientSecret) { 196 this.clientSecret = clientSecret; 197 return this; 198 } 199 getClientSecret()200 public String getClientSecret() { 201 return clientSecret; 202 } 203 build()204 public ClientId build() { 205 return new ClientId(clientId, clientSecret); 206 } 207 } 208 } 209