Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion hugegraph-api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@
</addDefaultSpecificationEntries>
</manifest>
<manifestEntries>
<Implementation-Version>0.62.0.0</Implementation-Version>
<Implementation-Version>0.63.0.0</Implementation-Version>
</manifestEntries>
</archive>
</configuration>
Expand Down
160 changes: 160 additions & 0 deletions hugegraph-api/src/main/java/com/baidu/hugegraph/api/auth/LoginAPI.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*
* Copyright 2017 HugeGraph Authors
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You 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 com.baidu.hugegraph.api.auth;

import javax.inject.Singleton;
import javax.security.sasl.AuthenticationException;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.NotAuthorizedException;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;

import com.baidu.hugegraph.HugeGraph;
import com.baidu.hugegraph.api.API;
import com.baidu.hugegraph.api.filter.AuthenticationFilter;
import com.baidu.hugegraph.api.filter.StatusFilter;
import com.baidu.hugegraph.api.filter.StatusFilter.Status;
import com.baidu.hugegraph.auth.AuthConstant;
import com.baidu.hugegraph.auth.UserWithRole;
import com.baidu.hugegraph.core.GraphManager;
import com.baidu.hugegraph.define.Checkable;
import com.baidu.hugegraph.server.RestServer;
import com.baidu.hugegraph.util.E;
import com.baidu.hugegraph.util.Log;
import com.codahale.metrics.annotation.Timed;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.ImmutableMap;

@Path("graphs/{graph}/auth")
@Singleton
public class LoginAPI extends API {

private static final Logger LOG = Log.logger(RestServer.class);

@POST
@Timed
@Path("login")
@Status(StatusFilter.Status.OK)
@Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON_WITH_CHARSET)
public String login(@Context GraphManager manager,
@PathParam("graph") String graph,
JsonLogin jsonLogin) {
LOG.debug("Graph [{}] user login: {}", graph, jsonLogin);
checkCreatingBody(jsonLogin);

try {
String token = manager.authManager()
.loginUser(jsonLogin.name, jsonLogin.password);
HugeGraph g = graph(manager, graph);
return manager.serializer(g)
.writeMap(ImmutableMap.of("token", token));
} catch (AuthenticationException e) {
throw new NotAuthorizedException(e.getMessage(), e);
}
}

@DELETE
@Timed
@Path("logout")
@Status(StatusFilter.Status.OK)
@Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON_WITH_CHARSET)
public void logout(@Context GraphManager manager,
@PathParam("graph") String graph,
@HeaderParam(HttpHeaders.AUTHORIZATION) String auth) {
E.checkArgument(StringUtils.isNotEmpty(auth),
"Request header Authorization must not be null");
LOG.debug("Graph [{}] user logout: {}", graph, auth);

if (!auth.startsWith(AuthenticationFilter.BEARER_TOKEN_PREFIX)) {
throw new BadRequestException(
"Only HTTP Bearer authentication is supported");
}

String token = auth.substring(AuthenticationFilter.BEARER_TOKEN_PREFIX
.length());

manager.authManager().logoutUser(token);
}

@GET
@Timed
@Path("verify")
@Status(StatusFilter.Status.OK)
@Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON_WITH_CHARSET)
public String verifyToken(@Context GraphManager manager,
@PathParam("graph") String graph,
@HeaderParam(HttpHeaders.AUTHORIZATION)
String token) {
E.checkArgument(StringUtils.isNotEmpty(token),
"Request header Authorization must not be null");
LOG.debug("Graph [{}] get user: {}", graph, token);

if (!token.startsWith(AuthenticationFilter.BEARER_TOKEN_PREFIX)) {
throw new BadRequestException(
"Only HTTP Bearer authentication is supported");
}

token = token.substring(AuthenticationFilter.BEARER_TOKEN_PREFIX
.length());
UserWithRole userWithRole = manager.authManager().validateUser(token);

HugeGraph g = graph(manager, graph);
return manager.serializer(g)
.writeMap(ImmutableMap.of(AuthConstant.TOKEN_USER_NAME,
userWithRole.username(),
AuthConstant.TOKEN_USER_ID,
userWithRole.userId()));
}

private static class JsonLogin implements Checkable {

@JsonProperty("user_name")
private String name;
@JsonProperty("user_password")
private String password;

@Override
public void checkCreate(boolean isBatch) {
E.checkArgument(!StringUtils.isEmpty(this.name),
"The name of user can't be null");
E.checkArgument(!StringUtils.isEmpty(this.password),
"The password of user can't be null");
}

@Override
public void checkUpdate() {
// pass
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ private static class JsonUser implements Checkable {
private String email;
@JsonProperty("user_avatar")
private String avatar;
@JsonProperty("user_description")
private String description;

public HugeUser build(HugeUser user) {
E.checkArgument(this.name == null || user.name().equals(this.name),
Expand All @@ -195,6 +197,9 @@ public HugeUser build(HugeUser user) {
if (this.avatar != null) {
user.avatar(this.avatar);
}
if (this.description != null) {
user.description(this.description);
}
return user;
}

Expand All @@ -204,6 +209,7 @@ public HugeUser build() {
user.phone(this.phone);
user.email(this.email);
user.avatar(this.avatar);
user.description(this.description);
return user;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@

import java.io.IOException;
import java.security.Principal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Priority;
import javax.ws.rs.BadRequestException;
Expand Down Expand Up @@ -51,15 +53,23 @@
import com.baidu.hugegraph.core.GraphManager;
import com.baidu.hugegraph.util.E;
import com.baidu.hugegraph.util.Log;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableList;

@Provider
@PreMatching
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {

public static final String BASIC_AUTH_PREFIX = "Basic ";
public static final String BEARER_TOKEN_PREFIX = "Bearer ";

private static final Logger LOG = Log.logger(AuthenticationFilter.class);

private static final List<String> WHITE_API_LIST = ImmutableList.of(
"auth/login",
"versions"
);

@Context
private javax.inject.Provider<GraphManager> managerProvider;

Expand All @@ -68,6 +78,9 @@ public class AuthenticationFilter implements ContainerRequestFilter {

@Override
public void filter(ContainerRequestContext context) throws IOException {
if (AuthenticationFilter.isWhiteAPI(context)) {
return;
}
User user = this.authenticate(context);
Authorizer authorizer = new Authorizer(user, context.getUriInfo());
context.setSecurityContext(authorizer);
Expand All @@ -91,46 +104,53 @@ protected User authenticate(ContainerRequestContext context) {
path = request.getRequestURI();
}

Map<String, String> credentials = new HashMap<>();
// Extract authentication credentials
String auth = context.getHeaderString(HttpHeaders.AUTHORIZATION);
if (auth == null) {
throw new NotAuthorizedException(
"Authentication credentials are required",
"Missing authentication credentials");
}
if (!auth.startsWith("Basic ")) {
throw new BadRequestException(
"Only HTTP Basic authentication is supported");
}

auth = auth.substring("Basic ".length());
auth = new String(DatatypeConverter.parseBase64Binary(auth),
Charsets.ASCII_CHARSET);
String[] values = auth.split(":");
if (values.length != 2) {
throw new BadRequestException(
"Invalid syntax for username and password");
}
if (auth.startsWith(BASIC_AUTH_PREFIX)) {
auth = auth.substring(BASIC_AUTH_PREFIX.length());
auth = new String(DatatypeConverter.parseBase64Binary(auth),
Charsets.ASCII_CHARSET);
String[] values = auth.split(":");
if (values.length != 2) {
throw new BadRequestException(
"Invalid syntax for username and password");
}

final String username = values[0];
final String password = values[1];

final String username = values[0];
final String password = values[1];
if (StringUtils.isEmpty(username) ||
StringUtils.isEmpty(password)) {
throw new BadRequestException(
"Invalid syntax for username and password");
}

if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
credentials.put(HugeAuthenticator.KEY_USERNAME, username);
credentials.put(HugeAuthenticator.KEY_PASSWORD, password);
} else if (auth.startsWith(BEARER_TOKEN_PREFIX)) {
String token = auth.substring(BEARER_TOKEN_PREFIX.length());
credentials.put(HugeAuthenticator.KEY_TOKEN, token);
} else {
throw new BadRequestException(
"Invalid syntax for username and password");
"Only HTTP Basic or Bearer authentication is supported");
}

credentials.put(HugeAuthenticator.KEY_ADDRESS, peer);
credentials.put(HugeAuthenticator.KEY_PATH, path);

// Validate the extracted credentials
try {
return manager.authenticate(ImmutableMap.of(
HugeAuthenticator.KEY_USERNAME, username,
HugeAuthenticator.KEY_PASSWORD, password,
HugeAuthenticator.KEY_ADDRESS, peer,
HugeAuthenticator.KEY_PATH, path));
return manager.authenticate(credentials);
} catch (AuthenticationException e) {
String msg = String.format("Authentication failed for user '%s'",
username);
throw new NotAuthorizedException(msg, e.getMessage());
throw new NotAuthorizedException("Authentication failed",
e.getMessage());
}
}

Expand Down Expand Up @@ -257,4 +277,18 @@ public boolean equals(Object obj) {
}
}
}

public static boolean isWhiteAPI(ContainerRequestContext context) {
String path = context.getUriInfo().getPath();

E.checkArgument(StringUtils.isNotEmpty(path),
"Invalid request uri '%s'", path);

for (String whiteApi : WHITE_API_LIST) {
if (path.endsWith(whiteApi)) {
return true;
}
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.apache.commons.lang.NotImplementedException;
import org.apache.tinkerpop.gremlin.groovy.jsr223.dsl.credential.CredentialGraphTokens;

import com.baidu.hugegraph.backend.id.IdGenerator;
import com.baidu.hugegraph.config.HugeConfig;
import com.baidu.hugegraph.config.ServerOptions;
import com.baidu.hugegraph.util.E;
Expand Down Expand Up @@ -58,12 +59,14 @@ public void setup(HugeConfig config) {
* @return String No permission if return ROLE_NONE else return a role
*/
@Override
public RolePermission authenticate(final String username,
final String password) {
public UserWithRole authenticate(final String username,
final String password,
final String token) {
E.checkArgumentNotNull(username,
"The username parameter can't be null");
E.checkArgumentNotNull(password,
"The password parameter can't be null");
E.checkArgument(token == null, "The token must be null");

RolePermission role;
if (password.equals(this.tokens.get(username))) {
Expand All @@ -77,7 +80,7 @@ public RolePermission authenticate(final String username,
role = ROLE_NONE;
}

return role;
return new UserWithRole(IdGenerator.of(username), username, role);
}

@Override
Expand Down
Loading