io.nitor.api.backend.msgraph.GraphSessionTokenService.java Source code

Java tutorial

Introduction

Here is the source code for io.nitor.api.backend.msgraph.GraphSessionTokenService.java

Source

/**
 * Copyright 2018 Nitor Creations Oy
 *
 * 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 io.nitor.api.backend.msgraph;

import io.vertx.core.Future;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpClientResponse;
import io.vertx.core.json.JsonObject;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import static io.netty.handler.codec.http.HttpHeaderNames.ACCEPT;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpHeaderValues.APPLICATION_JSON;
import static io.netty.handler.codec.http.HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED;
import static io.nitor.api.backend.util.Helpers.urlEncode;
import static io.vertx.core.Future.failedFuture;
import static io.vertx.core.Future.succeededFuture;
import static java.lang.System.currentTimeMillis;
import static java.util.concurrent.TimeUnit.SECONDS;

public class GraphSessionTokenService {
    private static final Logger logger = LogManager.getLogger(GraphSessionTokenService.class);
    private static final TokenData missingToken = new TokenData("<n/a>", -1, null);

    private final ConcurrentMap<String, TokenData> cache = new ConcurrentHashMap<>();
    private final HttpClient httpClient;
    private final String tokenUrl;
    private final String baseForm;

    public GraphSessionTokenService(HttpClient httpClient, JsonObject adConfig) {
        this.tokenUrl = adConfig.getJsonObject("openIdConfig").getString("token_endpoint");
        this.baseForm = "client_id=" + urlEncode(adConfig.getString("clientId")) + "&scope="
                + urlEncode(adConfig.getString("scope")) + "&grant_type=refresh_token" + "&client_secret="
                + urlEncode(adConfig.getString("clientSecret")) + "&redirect_uri="
                + urlEncode(adConfig.getString("redirectUri"));
        this.httpClient = httpClient;
    }

    public Future<TokenData> getAccessToken(String refreshToken) {
        if (refreshToken == null) {
            return failedFuture("refresh_token missing from session");
        }
        TokenData d = cache.getOrDefault(refreshToken, missingToken);
        if (d.expires > currentTimeMillis()) {
            return succeededFuture(d);
        }
        Buffer form = Buffer.buffer(baseForm + "&refresh_token=" + urlEncode(refreshToken));
        final Future<TokenData> ret = Future.future();
        httpClient.getAbs(tokenUrl).putHeader(ACCEPT, APPLICATION_JSON)
                .putHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED)
                .putHeader(CONTENT_LENGTH, String.valueOf(form.length())).setTimeout(SECONDS.toMillis(10))
                .exceptionHandler(err -> {
                    logger.error("Failed to refresh graph access token", err);
                    ret.fail(err);
                }).handler(resp -> handleRefreshResponse(resp, ret, d.refreshToken)).end(form);
        return ret;
    }

    private void handleRefreshResponse(HttpClientResponse resp, Future<TokenData> future,
            String previousRefreshToken) {
        resp.exceptionHandler(future::fail);
        resp.bodyHandler(body -> {
            if (resp.statusCode() != 200) {
                future.fail(body.toString());
                return;
            }
            JsonObject json = body.toJsonObject();
            TokenData d = new TokenData(json.getString("access_token"),
                    currentTimeMillis() + 1_000 * (json.getLong("expires_in") - 10),
                    json.getString("refresh_token"));
            cache.put(d.refreshToken, d);
            if (previousRefreshToken != null) {
                cache.put(previousRefreshToken, d);
            }
            future.complete(d);
        });
    }

    public static class TokenData {
        public final String accessToken;
        public final long expires;
        public final String refreshToken;

        public TokenData(String accessToken, long expires, String refreshToken) {
            this.accessToken = accessToken;
            this.expires = expires;
            this.refreshToken = refreshToken;
        }
    }
}