001package com.box.sdk;
002
003import static com.box.sdk.http.ContentType.APPLICATION_FORM_URLENCODED;
004
005import com.eclipsesource.json.Json;
006import com.eclipsesource.json.JsonObject;
007import java.net.URL;
008
009/**
010 * Creates and manages Client Credentials Grant API connection.
011 */
012public final class BoxCCGAPIConnection extends BoxAPIConnection {
013
014    static final String ENTERPRISE_SUBJECT_TYPE = "enterprise";
015    static final String USER_SUBJECT_TYPE = "user";
016    private String subjectType;
017    private String subjectId;
018
019    // Hiding constructor
020    private BoxCCGAPIConnection(String accessToken) {
021        super(accessToken);
022    }
023
024    // Hiding constructor
025    private BoxCCGAPIConnection(String clientID, String clientSecret, String accessToken, String refreshToken) {
026        super(clientID, clientSecret, accessToken, refreshToken);
027    }
028
029    // Hiding constructor
030    private BoxCCGAPIConnection(String clientID, String clientSecret, String authCode) {
031        super(clientID, clientSecret, authCode);
032    }
033
034    // Hiding constructor
035    private BoxCCGAPIConnection(String clientID, String clientSecret) {
036        super(clientID, clientSecret);
037    }
038
039    // Hiding constructor
040    private BoxCCGAPIConnection(BoxConfig boxConfig) {
041        super(boxConfig);
042    }
043
044    /**
045     * Creates connection that authenticates as a Service Account
046     *
047     * @param clientId     the client ID to use when getting the access token.
048     * @param clientSecret the client secret to use when getting the access token.
049     * @param enterpriseId the enterprise ID to use when getting the access token.
050     * @return Client Credentials Grant API connection.
051     */
052    public static BoxCCGAPIConnection applicationServiceAccountConnection(
053        String clientId, String clientSecret, String enterpriseId
054    ) {
055        BoxCCGAPIConnection api = new BoxCCGAPIConnection(clientId, clientSecret);
056        api.subjectType = ENTERPRISE_SUBJECT_TYPE;
057        api.subjectId = enterpriseId;
058        return api;
059    }
060
061    /**
062     * Creates connection that authenticates as a User
063     *
064     * @param clientId     the client ID to use when getting the access token.
065     * @param clientSecret the client secret to use when getting the access token.
066     * @param userId       the user ID to use when getting the access token.
067     * @return Client Credentials Grant API connection.
068     */
069    public static BoxCCGAPIConnection userConnection(String clientId, String clientSecret, String userId) {
070        BoxCCGAPIConnection api = new BoxCCGAPIConnection(clientId, clientSecret);
071        api.subjectType = USER_SUBJECT_TYPE;
072        api.subjectId = userId;
073        return api;
074    }
075
076    /**
077     * Restores a BoxAPIConnection from a saved state.
078     *
079     * @param clientID     the client ID to use with the connection.
080     * @param clientSecret the client secret to use with the connection.
081     * @param state        the saved state that was created with {@link #save}.
082     * @return a restored API connection.
083     * @see #save
084     */
085    public static BoxCCGAPIConnection restore(String clientID, String clientSecret, String state) {
086        BoxCCGAPIConnection api = new BoxCCGAPIConnection(clientID, clientSecret);
087        api.restore(state);
088        return api;
089    }
090
091    @Override
092    protected BoxAPIRequest createTokenRequest(URL url) {
093        String urlParameters = String.format(
094            "grant_type=client_credentials&client_id=%s&client_secret=%s&box_subject_type=%s&box_subject_id=%s",
095            this.getClientID(), this.getClientSecret(), this.subjectType, this.subjectId);
096        BoxAPIRequest request = new BoxAPIRequest(this, url, "POST");
097        request.shouldAuthenticate(false);
098        request.setBody(urlParameters);
099        request.addHeader("Content-Type", APPLICATION_FORM_URLENCODED);
100        return request;
101    }
102
103    @Override
104    protected void extractTokens(JsonObject jsonObject) {
105        this.setAccessToken(jsonObject.get("access_token").asString());
106        this.setLastRefresh(System.currentTimeMillis());
107        this.setExpires(jsonObject.get("expires_in").asLong() * 1000);
108    }
109
110    @Override
111    public boolean canRefresh() {
112        return true;
113    }
114
115    /**
116     * Set this API connection to make API calls on behalf of another users, impersonating them. This
117     * functionality can only be used when using service connection, otherwise it will throw
118     * IllegalStateException
119     *
120     * @param userID the ID of the user to act as.
121     * @throws IllegalStateException when called on User connection
122     */
123    @Override
124    public void asUser(String userID) {
125        if (isUserConnection()) {
126            throw new IllegalStateException("Cannot add As-User header to connection created for a user.");
127        }
128        super.asUser(userID);
129    }
130
131    /**
132     * Removes impersonation and returns connection to a service one. This undoes any previous calls to asUser().
133     *
134     * @throws IllegalStateException when called on User connection
135     * @see #asUser
136     */
137    @Override
138    public void asSelf() {
139        if (isUserConnection()) {
140            throw new IllegalStateException("Cannot remove As-User header from connection created for a user.");
141        }
142        super.asSelf();
143    }
144
145    public boolean isUserConnection() {
146        return subjectType.equals(USER_SUBJECT_TYPE);
147    }
148
149    @Override
150    public String save() {
151        JsonObject state = new JsonObject()
152            .add("accessToken", this.getAccessToken())
153            .add("lastRefresh", this.getLastRefresh())
154            .add("expires", this.getExpires())
155            .add("userAgent", this.getUserAgent())
156            .add("tokenURL", this.getTokenURL())
157            .add("baseURL", this.getBaseURL())
158            .add("baseUploadURL", this.getBaseUploadURL())
159            .add("autoRefresh", this.getAutoRefresh())
160            .add("maxRetryAttempts", this.getMaxRetryAttempts())
161            .add("subjectType", this.subjectType)
162            .add("subjectId", this.subjectId);
163        return state.toString();
164    }
165
166    @Override
167    public void restore(String state) {
168        JsonObject json = Json.parse(state).asObject();
169        String accessToken = json.get("accessToken").asString();
170        long lastRefresh = json.get("lastRefresh").asLong();
171        long expires = json.get("expires").asLong();
172        String userAgent = json.get("userAgent").asString();
173        String tokenURL = json.get("tokenURL").asString();
174        String baseURL = json.get("baseURL").asString();
175        String baseUploadURL = json.get("baseUploadURL").asString();
176        boolean autoRefresh = json.get("autoRefresh").asBoolean();
177        String subjectType = json.get("subjectType").asString();
178        String subjectId = json.get("subjectId").asString();
179
180        int maxRetryAttempts = -1;
181        if (json.names().contains("maxRetryAttempts")) {
182            maxRetryAttempts = json.get("maxRetryAttempts").asInt();
183        }
184
185        this.setAccessToken(accessToken);
186        setLastRefresh(lastRefresh);
187        setExpires(expires);
188        setUserAgent(userAgent);
189        setTokenURL(tokenURL);
190        setBaseURL(baseURL);
191        setBaseUploadURL(baseUploadURL);
192        setAutoRefresh(autoRefresh);
193        setMaxRetryAttempts(maxRetryAttempts);
194        this.subjectType = subjectType;
195        this.subjectId = subjectId;
196
197    }
198}