Skip to content
Open
5 changes: 4 additions & 1 deletion amoro-ams/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,10 @@
<version>1.19.6</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.casbin</groupId>
<artifactId>jcasbin</artifactId>
</dependency>

</dependencies>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
import org.apache.amoro.server.dashboard.utils.CommonUtil;
import org.apache.amoro.server.manager.EventsManager;
import org.apache.amoro.server.manager.MetricManager;
import org.apache.amoro.server.permission.PermissionManager;
import org.apache.amoro.server.permission.UserInfoManager;
import org.apache.amoro.server.persistence.DataSourceFactory;
import org.apache.amoro.server.persistence.HttpSessionHandlerFactory;
import org.apache.amoro.server.persistence.SqlSessionFactoryProvider;
Expand Down Expand Up @@ -109,6 +111,8 @@ public class AmoroServiceContainer {
private TServer optimizingServiceServer;
private Javalin httpServer;
private AmsServiceMetrics amsServiceMetrics;
private UserInfoManager userInfoManager;
private PermissionManager permissionManager;

public AmoroServiceContainer() throws Exception {
initConfig();
Expand Down Expand Up @@ -163,7 +167,8 @@ public void startService() throws Exception {

optimizingService =
new DefaultOptimizingService(serviceConfig, catalogManager, optimizerManager, tableService);

userInfoManager = new UserInfoManager();
permissionManager = new PermissionManager();
LOG.info("Setting up AMS table executors...");
AsyncTableExecutors.getInstance().setup(tableService, serviceConfig);
addHandlerChain(optimizingService.getTableRuntimeHandler());
Expand Down Expand Up @@ -262,7 +267,9 @@ private void initHttpService() {
tableManager,
optimizerManager,
optimizingService,
terminalManager);
terminalManager,
userInfoManager,
permissionManager);
RestCatalogService restCatalogService = new RestCatalogService(catalogManager, tableManager);

httpServer =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import io.javalin.http.staticfiles.Location;
import io.javalin.http.staticfiles.StaticFileConfig;
import org.apache.amoro.config.Configurations;
import org.apache.amoro.exception.AccessDeniedException;
import org.apache.amoro.exception.ForbiddenException;
import org.apache.amoro.exception.SignatureCheckException;
import org.apache.amoro.server.AmoroManagementConf;
Expand All @@ -49,8 +50,11 @@
import org.apache.amoro.server.dashboard.controller.TableController;
import org.apache.amoro.server.dashboard.controller.TerminalController;
import org.apache.amoro.server.dashboard.controller.VersionController;
import org.apache.amoro.server.dashboard.model.SessionInfo;
import org.apache.amoro.server.dashboard.response.ErrorResponse;
import org.apache.amoro.server.dashboard.utils.ParamSignatureCalculator;
import org.apache.amoro.server.permission.PermissionManager;
import org.apache.amoro.server.permission.UserInfoManager;
import org.apache.amoro.server.resource.OptimizerManager;
import org.apache.amoro.server.table.TableManager;
import org.apache.amoro.server.terminal.TerminalManager;
Expand Down Expand Up @@ -93,18 +97,22 @@ public class DashboardServer {
private final String authType;
private final String basicAuthUser;
private final String basicAuthPassword;
private final UserInfoManager userInfoManager;
private final PermissionManager permissionManager;

public DashboardServer(
Configurations serviceConfig,
CatalogManager catalogManager,
TableManager tableManager,
OptimizerManager optimizerManager,
DefaultOptimizingService optimizingService,
TerminalManager terminalManager) {
TerminalManager terminalManager,
UserInfoManager userInfoManager,
PermissionManager permissionManager) {
PlatformFileManager platformFileManager = new PlatformFileManager();
this.catalogController = new CatalogController(catalogManager, platformFileManager);
this.healthCheckController = new HealthCheckController();
this.loginController = new LoginController(serviceConfig);
this.loginController = new LoginController(serviceConfig, userInfoManager);
// TODO: remove table service from OptimizerGroupController
this.optimizerGroupController =
new OptimizerGroupController(tableManager, optimizingService, optimizerManager);
Expand All @@ -124,6 +132,8 @@ public DashboardServer(
this.authType = serviceConfig.get(AmoroManagementConf.HTTP_SERVER_REST_AUTH_TYPE);
this.basicAuthUser = serviceConfig.get(AmoroManagementConf.ADMIN_USERNAME);
this.basicAuthPassword = serviceConfig.get(AmoroManagementConf.ADMIN_PASSWORD);
this.userInfoManager = userInfoManager;
this.permissionManager = permissionManager;
}

private volatile String indexHtml = null;
Expand Down Expand Up @@ -387,15 +397,26 @@ public void preHandleRequest(Context ctx) {
if (null == ctx.sessionAttribute("user")) {
throw new ForbiddenException("User session attribute is missed for url: " + uriPath);
}
// TODO : check permission
SessionInfo user = ctx.sessionAttribute("user");
String method = ctx.method();
String path = ctx.path();
if (!permissionManager.accessible(user.getUserName(), path, method)) {
throw new AccessDeniedException("unable to access url: " + uriPath);
}
return;
}
if (AUTH_TYPE_BASIC.equalsIgnoreCase(authType)) {
BasicAuthCredentials cred = ctx.basicAuthCredentials();
if (!(basicAuthUser.equals(cred.component1())
&& basicAuthPassword.equals(cred.component2()))) {
if (!userInfoManager.isValidate(cred.component1(), cred.component2())) {
throw new SignatureCheckException(
"Failed to authenticate via basic authentication for url:" + uriPath);
}
// if (!(basicAuthUser.equals(cred.component1())
// && basicAuthPassword.equals(cred.component2()))) {
// throw new SignatureCheckException(
// "Failed to authenticate via basic authentication for url:" + uriPath);
// }
} else {
checkApiToken(
ctx.url(), ctx.queryParam("apiKey"), ctx.queryParam("signature"), ctx.queryParamMap());
Expand All @@ -412,6 +433,14 @@ public void handleException(Exception e, Context ctx) {
}
} else if (e instanceof SignatureCheckException) {
ctx.json(new ErrorResponse(HttpCode.FORBIDDEN, "Signature check failed", ""));
} else if (e instanceof AccessDeniedException) {
if (!ctx.req.getRequestURI().startsWith("/api/ams")) {
ctx.html(getIndexFileContent());
} else {
ctx.status(HttpCode.FORBIDDEN);
ctx.json(new ErrorResponse(HttpCode.FORBIDDEN, "Access Denied", ""));
return;
}
} else {
ctx.json(new ErrorResponse(HttpCode.INTERNAL_SERVER_ERROR, e.getMessage(), ""));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,23 @@
import io.javalin.http.Context;
import org.apache.amoro.config.Configurations;
import org.apache.amoro.server.AmoroManagementConf;
import org.apache.amoro.server.dashboard.model.SessionInfo;
import org.apache.amoro.server.dashboard.response.OkResponse;
import org.apache.amoro.server.permission.UserInfoManager;

import java.io.Serializable;
import java.util.Map;

/** The controller that handles login requests. */
public class LoginController {

private final String adminUser;
private final String adminPassword;
private final UserInfoManager userInfoManager;

public LoginController(Configurations serviceConfig) {
public LoginController(Configurations serviceConfig, UserInfoManager userInfoManager) {
adminUser = serviceConfig.get(AmoroManagementConf.ADMIN_USERNAME);
adminPassword = serviceConfig.get(AmoroManagementConf.ADMIN_PASSWORD);
this.userInfoManager = userInfoManager;
}

/** Get current user. */
Expand All @@ -49,8 +52,8 @@ public void login(Context ctx) {
Map<String, String> bodyParams = ctx.bodyAsClass(Map.class);
String user = bodyParams.get("user");
String pwd = bodyParams.get("password");
if (adminUser.equals(user) && (adminPassword.equals(pwd))) {
ctx.sessionAttribute("user", new SessionInfo(adminUser, System.currentTimeMillis() + ""));
if (userInfoManager.isValidate(user, pwd)) {
ctx.sessionAttribute("user", new SessionInfo(user, System.currentTimeMillis() + ""));
ctx.json(OkResponse.of("success"));
} else {
throw new RuntimeException("bad user " + user + " or password!");
Expand All @@ -62,30 +65,4 @@ public void logout(Context ctx) {
ctx.removeCookie("JSESSIONID");
ctx.json(OkResponse.ok());
}

static class SessionInfo implements Serializable {
String userName;
String loginTime;

public SessionInfo(String username, String loginTime) {
this.userName = username;
this.loginTime = loginTime;
}

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

public String getLoginTime() {
return loginTime;
}

public void setLoginTime(String loginTime) {
this.loginTime = loginTime;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,35 @@

public class SessionInfo {
private String sessionId;
String userName;

public String getLoginTime() {
return loginTime;
}

public void setLoginTime(String loginTime) {
this.loginTime = loginTime;
}

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

String loginTime;

public SessionInfo(String sessionId) {
this.sessionId = sessionId;
}

public SessionInfo(String userName, String loginTime) {
this.userName = userName;
this.loginTime = loginTime;
}

public SessionInfo() {}

public String getSessionId() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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 org.apache.amoro.server.permission;

import org.apache.amoro.server.Environments;
import org.casbin.jcasbin.main.Enforcer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;

public class PermissionManager {

public static final Logger LOG = LoggerFactory.getLogger(UserInfoManager.class);

private final Enforcer enforcer;

public PermissionManager() {
String modelPath = Environments.getConfigPath() + "/rbac_model.conf";
String policyPath = Environments.getConfigPath() + "/policy.csv";
File modelFile = new File(modelPath);
File policyFile = new File(policyPath);
if (!modelFile.exists() || !policyFile.exists()) {
enforcer = new Enforcer();
LOG.warn("model or policy file not exist, please check your config");
return;
}
enforcer = new Enforcer(modelPath, policyPath);
}

public boolean accessible(String user, String url, String method) {
if (!enforcer.enforce(user, url, method)) {
return false;
}
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* 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 org.apache.amoro.server.permission;

import org.apache.amoro.server.Environments;
import org.apache.amoro.shade.guava32.com.google.common.collect.Maps;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.util.Map;

public class UserInfoManager {

public static final Logger LOG = LoggerFactory.getLogger(UserInfoManager.class);

private final Map<String, String> users = Maps.newHashMap();

public UserInfoManager() {
String configPath = Environments.getConfigPath() + "/users.csv";
this.loadUserInfoFileToMap(configPath);
}

public boolean isValidate(String username, String password) {
if (users.containsKey(username)) {
return users.get(username).equals(password);
}
return false;
}

private void loadUserInfoFileToMap(String filePath) {
try {
File file = new File(filePath);
if (!file.exists()) {
LOG.warn("userInfo file not exist, please check your config");
return;
}
FileUtils.readLines(file, "UTF-8")
.forEach(
line -> {
String[] parts = line.split(",");
if (parts.length == 2) {
String username = parts[0].trim();
String password = parts[1].trim();
users.put(username, password);
}
});
} catch (Exception e) {
LOG.error("load userInfo file error", e);
throw new RuntimeException("load userInfo file error", e);
}
}
}
Loading
Loading