001package com.box.sdkgen.box.jwtauth;
002
003import static com.box.sdkgen.internal.utils.UtilsManager.createJwtAssertion;
004import static com.box.sdkgen.internal.utils.UtilsManager.entryOf;
005import static com.box.sdkgen.internal.utils.UtilsManager.getEpochTimeInSeconds;
006import static com.box.sdkgen.internal.utils.UtilsManager.getUuid;
007import static com.box.sdkgen.internal.utils.UtilsManager.isBrowser;
008import static com.box.sdkgen.internal.utils.UtilsManager.mapOf;
009
010import com.box.sdkgen.box.errors.BoxSDKError;
011import com.box.sdkgen.box.tokenstorage.InMemoryTokenStorage;
012import com.box.sdkgen.box.tokenstorage.TokenStorage;
013import com.box.sdkgen.internal.utils.JwtKey;
014import com.box.sdkgen.internal.utils.JwtSignOptions;
015import com.box.sdkgen.managers.authorization.AuthorizationManager;
016import com.box.sdkgen.networking.auth.Authentication;
017import com.box.sdkgen.networking.network.NetworkSession;
018import com.box.sdkgen.schemas.accesstoken.AccessToken;
019import com.box.sdkgen.schemas.postoauth2revoke.PostOAuth2Revoke;
020import com.box.sdkgen.schemas.postoauth2token.PostOAuth2Token;
021import com.box.sdkgen.schemas.postoauth2token.PostOAuth2TokenGrantTypeField;
022import com.box.sdkgen.schemas.postoauth2token.PostOAuth2TokenSubjectTokenTypeField;
023import java.util.List;
024import java.util.Map;
025
026public class BoxJWTAuth implements Authentication {
027
028  /** An object containing all JWT configuration to use for authentication */
029  public final JWTConfig config;
030
031  /**
032   * An object responsible for storing token. If no custom implementation provided, the token will
033   * be stored in memory.
034   */
035  public final TokenStorage tokenStorage;
036
037  /**
038   * The ID of the user or enterprise to authenticate as. If not provided, defaults to the
039   * enterprise ID if set, otherwise defaults to the user ID.
040   */
041  public String subjectId;
042
043  /** The type of the subject ID provided. Must be either 'user' or 'enterprise'. */
044  public String subjectType;
045
046  public BoxJWTAuth(JWTConfig config) {
047    this.config = config;
048    this.tokenStorage = this.config.getTokenStorage();
049    this.subjectId =
050        (!(this.config.getEnterpriseId() == null)
051            ? this.config.getEnterpriseId()
052            : this.config.getUserId());
053    this.subjectType = (!(this.config.getEnterpriseId() == null) ? "enterprise" : "user");
054  }
055
056  /** Get new access token using JWT auth. */
057  public AccessToken refreshToken() {
058    return refreshToken(null);
059  }
060
061  /**
062   * Get new access token using JWT auth.
063   *
064   * @param networkSession An object to keep network session state
065   */
066  @Override
067  public AccessToken refreshToken(NetworkSession networkSession) {
068    if (isBrowser()) {
069      throw new BoxSDKError("JWT auth is not supported in browser environment.");
070    }
071    Map<String, Object> claims =
072        mapOf(
073            entryOf("exp", getEpochTimeInSeconds() + 30),
074            entryOf("box_sub_type", this.subjectType));
075    JwtSignOptions jwtOptions =
076        new JwtSignOptions(
077            this.config.getAlgorithm(),
078            "https://api.box.com/oauth2/token",
079            this.config.getClientId(),
080            this.subjectId,
081            getUuid(),
082            this.config.getJwtKeyId(),
083            this.config.getPrivateKeyDecryptor());
084    JwtKey jwtKey = new JwtKey(this.config.getPrivateKey(), this.config.getPrivateKeyPassphrase());
085    String assertion = createJwtAssertion(claims, jwtKey, jwtOptions);
086    AuthorizationManager authManager =
087        new AuthorizationManager.Builder()
088            .networkSession((!(networkSession == null) ? networkSession : new NetworkSession()))
089            .build();
090    AccessToken token =
091        authManager.requestAccessToken(
092            new PostOAuth2Token.Builder(
093                    PostOAuth2TokenGrantTypeField.URN_IETF_PARAMS_OAUTH_GRANT_TYPE_JWT_BEARER)
094                .assertion(assertion)
095                .clientId(this.config.getClientId())
096                .clientSecret(this.config.getClientSecret())
097                .build());
098    this.tokenStorage.store(token);
099    return token;
100  }
101
102  /**
103   * Get the current access token. If the current access token is expired or not found, this method
104   * will attempt to refresh the token.
105   */
106  public AccessToken retrieveToken() {
107    return retrieveToken(null);
108  }
109
110  /**
111   * Get the current access token. If the current access token is expired or not found, this method
112   * will attempt to refresh the token.
113   *
114   * @param networkSession An object to keep network session state
115   */
116  @Override
117  public AccessToken retrieveToken(NetworkSession networkSession) {
118    AccessToken oldToken = this.tokenStorage.get();
119    if (oldToken == null) {
120      AccessToken newToken = this.refreshToken(networkSession);
121      return newToken;
122    }
123    return oldToken;
124  }
125
126  public String retrieveAuthorizationHeader() {
127    return retrieveAuthorizationHeader(null);
128  }
129
130  @Override
131  public String retrieveAuthorizationHeader(NetworkSession networkSession) {
132    AccessToken token = this.retrieveToken(networkSession);
133    return String.join("", "Bearer ", token.getAccessToken());
134  }
135
136  /**
137   * Create a new BoxJWTAuth instance that uses the provided user ID as the subject of the JWT
138   * assertion. May be one of this application's created App User. Depending on the configured User
139   * Access Level, may also be any other App User or Managed User in the enterprise.
140   * &lt;https://developer.box.com/en/guides/applications/&gt;
141   * &lt;https://developer.box.com/en/guides/authentication/select/&gt;
142   *
143   * @param userId The id of the user to authenticate
144   */
145  public BoxJWTAuth withUserSubject(String userId) {
146    return withUserSubject(userId, new InMemoryTokenStorage());
147  }
148
149  /**
150   * Create a new BoxJWTAuth instance that uses the provided user ID as the subject of the JWT
151   * assertion. May be one of this application's created App User. Depending on the configured User
152   * Access Level, may also be any other App User or Managed User in the enterprise.
153   * &lt;https://developer.box.com/en/guides/applications/&gt;
154   * &lt;https://developer.box.com/en/guides/authentication/select/&gt;
155   *
156   * @param userId The id of the user to authenticate
157   * @param tokenStorage Object responsible for storing token in newly created BoxJWTAuth. If no
158   *     custom implementation provided, the token will be stored in memory.
159   */
160  public BoxJWTAuth withUserSubject(String userId, TokenStorage tokenStorage) {
161    JWTConfig newConfig =
162        new JWTConfig.Builder(
163                this.config.getClientId(),
164                this.config.getClientSecret(),
165                this.config.getJwtKeyId(),
166                this.config.getPrivateKey(),
167                this.config.getPrivateKeyPassphrase())
168            .enterpriseId(null)
169            .userId(userId)
170            .tokenStorage(tokenStorage)
171            .build();
172    BoxJWTAuth newAuth = new BoxJWTAuth(newConfig);
173    return newAuth;
174  }
175
176  /**
177   * Create a new BoxJWTAuth instance that uses the provided enterprise ID as the subject of the JWT
178   * assertion.
179   *
180   * @param enterpriseId The id of the enterprise to authenticate
181   */
182  public BoxJWTAuth withEnterpriseSubject(String enterpriseId) {
183    return withEnterpriseSubject(enterpriseId, new InMemoryTokenStorage());
184  }
185
186  /**
187   * Create a new BoxJWTAuth instance that uses the provided enterprise ID as the subject of the JWT
188   * assertion.
189   *
190   * @param enterpriseId The id of the enterprise to authenticate
191   * @param tokenStorage Object responsible for storing token in newly created BoxJWTAuth. If no
192   *     custom implementation provided, the token will be stored in memory.
193   */
194  public BoxJWTAuth withEnterpriseSubject(String enterpriseId, TokenStorage tokenStorage) {
195    JWTConfig newConfig =
196        new JWTConfig.Builder(
197                this.config.getClientId(),
198                this.config.getClientSecret(),
199                this.config.getJwtKeyId(),
200                this.config.getPrivateKey(),
201                this.config.getPrivateKeyPassphrase())
202            .enterpriseId(enterpriseId)
203            .userId(null)
204            .tokenStorage(tokenStorage)
205            .build();
206    BoxJWTAuth newAuth = new BoxJWTAuth(newConfig);
207    return newAuth;
208  }
209
210  /**
211   * Downscope access token to the provided scopes. Returning a new access token with the provided
212   * scopes, with the original access token unchanged.
213   *
214   * @param scopes The scope(s) to apply to the resulting token.
215   * @param resource The file or folder to get a downscoped token for. If None and shared_link None,
216   *     the resulting token will not be scoped down to just a single item. The resource should be a
217   *     full URL to an item, e.g. https://api.box.com/2.0/files/123456.
218   * @param sharedLink The shared link to get a downscoped token for. If None and item None, the
219   *     resulting token will not be scoped down to just a single item.
220   * @param networkSession An object to keep network session state
221   */
222  @Override
223  public AccessToken downscopeToken(
224      List<String> scopes, String resource, String sharedLink, NetworkSession networkSession) {
225    AccessToken token = this.retrieveToken(networkSession);
226    if (token == null) {
227      throw new BoxSDKError(
228          "No access token is available. Make an API call to retrieve a token before calling this method.");
229    }
230    AuthorizationManager authManager =
231        new AuthorizationManager.Builder()
232            .networkSession((!(networkSession == null) ? networkSession : new NetworkSession()))
233            .build();
234    AccessToken downscopedToken =
235        authManager.requestAccessToken(
236            new PostOAuth2Token.Builder(
237                    PostOAuth2TokenGrantTypeField.URN_IETF_PARAMS_OAUTH_GRANT_TYPE_TOKEN_EXCHANGE)
238                .subjectToken(token.getAccessToken())
239                .subjectTokenType(
240                    PostOAuth2TokenSubjectTokenTypeField
241                        .URN_IETF_PARAMS_OAUTH_TOKEN_TYPE_ACCESS_TOKEN)
242                .resource(resource)
243                .scope(String.join(" ", scopes))
244                .boxSharedLink(sharedLink)
245                .build());
246    return downscopedToken;
247  }
248
249  /** Revoke the current access token and remove it from token storage. */
250  public void revokeToken() {
251    revokeToken(null);
252  }
253
254  /**
255   * Revoke the current access token and remove it from token storage.
256   *
257   * @param networkSession An object to keep network session state
258   */
259  @Override
260  public void revokeToken(NetworkSession networkSession) {
261    AccessToken oldToken = this.tokenStorage.get();
262    if (oldToken == null) {
263      return;
264    }
265    AuthorizationManager authManager =
266        new AuthorizationManager.Builder()
267            .networkSession((!(networkSession == null) ? networkSession : new NetworkSession()))
268            .build();
269    authManager.revokeAccessToken(
270        new PostOAuth2Revoke.Builder()
271            .clientId(this.config.getClientId())
272            .clientSecret(this.config.getClientSecret())
273            .token(oldToken.getAccessToken())
274            .build());
275    this.tokenStorage.clear();
276  }
277
278  public JWTConfig getConfig() {
279    return config;
280  }
281
282  public TokenStorage getTokenStorage() {
283    return tokenStorage;
284  }
285}