org.janusgraph.graphdb.tinkerpop.gremlin.server.handler.HttpHMACAuthenticationHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.janusgraph.graphdb.tinkerpop.gremlin.server.handler.HttpHMACAuthenticationHandler.java

Source

// Copyright 2017 JanusGraph Authors
//
// 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 org.janusgraph.graphdb.tinkerpop.gremlin.server.handler;

import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpResponseStatus.UNAUTHORIZED;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import static org.apache.tinkerpop.gremlin.groovy.jsr223.dsl.credential.CredentialGraphTokens.PROPERTY_PASSWORD;
import static org.apache.tinkerpop.gremlin.groovy.jsr223.dsl.credential.CredentialGraphTokens.PROPERTY_USERNAME;

import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

import org.apache.tinkerpop.gremlin.server.auth.Authenticator;
import org.apache.tinkerpop.gremlin.server.handler.AbstractAuthenticationHandler;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.util.ReferenceCountUtil;

/**
 * An Authentication Handler intended to be used with the HMACAuthenticator
 * to provide Basic Auth and Token auth using an HMAC token
 *
 * @author Keith Lohnes lohnesk@gmail.com
 */
public class HttpHMACAuthenticationHandler extends AbstractAuthenticationHandler {

    private final Base64.Decoder decoder = Base64.getUrlDecoder();

    private final String basic = "Basic ";
    private final String token = "Token ";

    public static final String PROPERTY_TOKEN = "TOKEN";
    public static final String PROPERTY_GENERATE_TOKEN = "GENERATE_TOKEN";

    public HttpHMACAuthenticationHandler(final Authenticator authenticator) {
        super(authenticator);
    }

    @Override
    public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
        if (msg instanceof FullHttpRequest) {
            final FullHttpRequest req = (FullHttpRequest) msg;
            final HttpMethod method = req.getMethod();
            final Map<String, String> credentialsMap = getCredentialsMap(ctx, req);
            if (credentialsMap == null) {
                sendError(ctx, msg);
                return;
            }
            if ("/session".equals(req.getUri()) && method.equals(HttpMethod.GET)) {
                try {
                    credentialsMap.put(PROPERTY_GENERATE_TOKEN, "true");
                    authenticator.authenticate(credentialsMap);
                } catch (Exception e) {
                    sendError(ctx, msg);
                    return;
                }
                replyWithToken(ctx, msg, credentialsMap.get(PROPERTY_TOKEN));
            } else {
                try {
                    authenticator.authenticate(credentialsMap);
                    ctx.fireChannelRead(req);
                } catch (Exception e) {
                    sendError(ctx, msg);
                    return;
                }
            }
        }
    }

    private Map<String, String> getCredentialsMap(final ChannelHandlerContext ctx, final FullHttpRequest req) {
        // strip off "Basic " from the Authorization header (RFC 2617)
        // or "Token "
        final String authorizationHeader = req.headers().get("Authorization");
        if (authorizationHeader == null) {
            return null;
        }
        if (!(authorizationHeader.startsWith(basic) || authorizationHeader.startsWith(token))) {
            return null;
        }
        final String authType = authorizationHeader.startsWith(basic) ? basic : token;
        return getAuthCredMap(authorizationHeader, authType);
    }

    private Map<String, String> getAuthCredMap(final String authorizationHeader, final String authType) {
        final byte[] decodedAuthParams;
        final String authorization;
        try {
            final String encodedAuthParams = authorizationHeader.substring(authType.length());
            decodedAuthParams = decoder.decode(encodedAuthParams);
            authorization = new String(decodedAuthParams);
        } catch (IndexOutOfBoundsException | IllegalArgumentException e) {
            return null;
        }
        final Map<String, String> credentials = new HashMap<>();

        if (authType.equals(basic)) {
            final String[] split = authorization.split(":");
            if (split.length != 2) {
                return null;
            }
            credentials.put(PROPERTY_USERNAME, split[0]);
            credentials.put(PROPERTY_PASSWORD, split[1]);
        } else {
            credentials.put(PROPERTY_TOKEN, authorization);
        }
        return credentials;
    }

    private void sendError(final ChannelHandlerContext ctx, final Object msg) {
        ctx.writeAndFlush(new DefaultFullHttpResponse(HTTP_1_1, UNAUTHORIZED))
                .addListener(ChannelFutureListener.CLOSE);
        ReferenceCountUtil.release(msg);
    }

    private void replyWithToken(final ChannelHandlerContext ctx, final Object msg, final String token) {
        final String json = "{\"token\": \"" + token + "\"}";
        final byte[] jsonBytes = json.getBytes();
        final FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK,
                Unpooled.wrappedBuffer(jsonBytes));
        response.headers().set(CONTENT_TYPE, "application/json");
        response.headers().set(CONTENT_LENGTH, response.content().readableBytes());
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
        ReferenceCountUtil.release(msg);
    }
}