Skip to content

Commit

Permalink
Merge pull request #939 from virtualcell/938-admin-usage-web-page
Browse files Browse the repository at this point in the history
new AdminStats web page
  • Loading branch information
jcschaff committed Jul 12, 2023
2 parents d521b46 + ff3b863 commit c216b42
Show file tree
Hide file tree
Showing 11 changed files with 126 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ private List<User> getUsers(String singleUsername, String startingUsername, bool
throws DataAccessException, SQLException {

if (singleUsername != null){
User user = adminDbTopLevel.getUser(singleUsername, true);
User.SpecialUser user = adminDbTopLevel.getUser(singleUsername, true);
if (user == null){
throw new RuntimeException("failed to find user "+singleUsername);
}
Expand Down
24 changes: 21 additions & 3 deletions vcell-api/src/main/java/org/vcell/rest/VCellApiApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.restlet.security.ChallengeAuthenticator;
import org.vcell.rest.admin.AdminJobsRestlet;
import org.vcell.rest.admin.AdminService;
import org.vcell.rest.admin.AdminStatsRestlet;
import org.vcell.rest.auth.AuthenticationTokenRestlet;
import org.vcell.rest.auth.BearerTokenVerifier;
import org.vcell.rest.auth.CookieVerifier;
Expand All @@ -29,6 +30,7 @@
import org.vcell.rest.rpc.RpcService;
import org.vcell.rest.server.*;
import org.vcell.rest.users.*;
import org.vcell.util.DataAccessException;
import org.vcell.util.document.KeyValue;
import org.vcell.util.document.User;

Expand Down Expand Up @@ -127,7 +129,8 @@ public enum AuthenticationPolicy {

public static final String ADMIN = "admin";
public static final String ADMIN_JOBS = "jobs";

public static final String ADMIN_STATS = "stats";

public static final String JOBINDEX = "jobindex";

public static final String SAVESIMULATION = "save";
Expand Down Expand Up @@ -309,7 +312,8 @@ public Restlet createInboundRoot() {

rootRouter.attach("/"+HEALTH, new HealthRestlet(getContext()));

rootRouter.attach("/"+ADMIN+"/"+ADMIN_JOBS, new AdminJobsRestlet(getContext()));
rootRouter.attach("/"+ADMIN+"/"+ADMIN_JOBS, new AdminJobsRestlet(getContext()));
rootRouter.attach("/"+ADMIN+"/"+ADMIN_STATS, new AdminStatsRestlet(getContext(), restDatabaseService));

rootRouter.attach("/auth/user", new Restlet(getContext()){

Expand Down Expand Up @@ -372,7 +376,21 @@ public AdminService getAdminService() {
return this.adminService;
}

public User.SPECIAL_CLAIM[] getSpecialClaims(ApiAccessToken apiAccessToken) throws DataAccessException {
User user = apiAccessToken.getUser();
return restDatabaseService.getSpecialClaims(user);
}

public User getVCellUser(ChallengeResponse response, AuthenticationPolicy authPolicy) throws ResourceException {
ApiAccessToken apiAccessToken = getApiAccessToken(response, authPolicy);
if (apiAccessToken != null){
return apiAccessToken.getUser();
} else {
return null;
}
}

public ApiAccessToken getApiAccessToken(ChallengeResponse response, AuthenticationPolicy authPolicy) throws ResourceException {
try {
ApiAccessToken accessToken = bearerTokenVerifier.getApiAccessToken(response);
if (accessToken == null){
Expand All @@ -395,7 +413,7 @@ public User getVCellUser(ChallengeResponse response, AuthenticationPolicy authPo
}
}else{
getLogger().log(Level.FINE,"VCellApiApplication.getVCellUser(response) - ApiAccessToken is valid ... returning user = "+accessToken.getUser().getName());
return accessToken.getUser();
return accessToken;
}
}else{ // accessToken is null
if (authPolicy == AuthenticationPolicy.ignoreInvalidCredentials){
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package org.vcell.rest.admin;

import cbit.vcell.modeldb.ApiAccessToken;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.Restlet;
import org.restlet.data.MediaType;
import org.restlet.data.Method;
import org.restlet.data.Reference;
import org.restlet.data.Status;
import org.vcell.rest.VCellApiApplication;
import org.vcell.rest.rpc.RpcRestlet;
import org.vcell.rest.server.RestDatabaseService;
import org.vcell.util.document.User;

import java.util.Arrays;

public class AdminStatsRestlet extends Restlet {
private static Logger lg = LogManager.getLogger(RpcRestlet.class);
private RestDatabaseService restDatabaseService;

public AdminStatsRestlet(Context context, RestDatabaseService restDatabaseService) {
super(context);
this.restDatabaseService = restDatabaseService;
}

@Override
public void handle(Request req, Response response) {
if (req.getMethod().equals(Method.GET)) {
try {
VCellApiApplication application = ((VCellApiApplication) getApplication());
ApiAccessToken token = application.getApiAccessToken(req.getChallengeResponse(), VCellApiApplication.AuthenticationPolicy.ignoreInvalidCredentials);
String loginUrl = "/" + VCellApiApplication.LOGINFORM +
"?" + VCellApiApplication.REDIRECTURL_FORMNAME +
"=" + Reference.encode(req.getResourceRef().toUrl().toString());
String logoutUrl = "/" + VCellApiApplication.LOGOUT +
"?" + VCellApiApplication.REDIRECTURL_FORMNAME +
"=" + Reference.encode(req.getResourceRef().toUrl().toString());
if (token == null) {
response.setStatus(Status.CLIENT_ERROR_UNAUTHORIZED);
response.setEntity("<h2>must login to access this content, " +
"click <a href=\""+loginUrl+"\"> here </a> to log in</h2>" +
"<h2>note that it takes 5-10 seconds to generate the report after login - please be patient</h2>", MediaType.TEXT_HTML);
return;
}
User user = application.getVCellUser(req.getChallengeResponse(), VCellApiApplication.AuthenticationPolicy.prohibitInvalidCredentials);
User.SPECIAL_CLAIM[] mySpecials = application.getSpecialClaims(token);
if (mySpecials==null || !Arrays.stream(mySpecials).anyMatch(s -> (s == User.SPECIAL_CLAIM.admins))) {
response.setStatus(Status.CLIENT_ERROR_UNAUTHORIZED);
response.setEntity("<h2>account '"+user.getName()+"' has insufficient privilege</h2>" +
"<h2> click <a href=\""+logoutUrl+"\"> here </a> to log out</h2>",
MediaType.TEXT_HTML);
return;
}
String htmlReport = restDatabaseService.getBasicStatistics();
response.setStatus(Status.SUCCESS_OK);
response.setEntity(htmlReport, MediaType.TEXT_HTML);
} catch (Exception e) {
String errMesg = "<html><body>Error RpcRestlet.handle(...) req='" + req.toString() + "' <br>err='" + e.getMessage() + "'</br>" + "</body></html>";
getLogger().severe(errMesg);
lg.error(e.getMessage(), e);
response.setStatus(Status.SERVER_ERROR_INTERNAL);
response.setEntity(errMesg, MediaType.TEXT_HTML);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ public RestDatabaseService(DatabaseServerImpl databaseServerImpl, LocalAdminDbSe

}

public User.SPECIAL_CLAIM[] getSpecialClaims(User user) throws DataAccessException {
User.SpecialUser userWithClaims = localAdminDbServer.getUser(user.getName());
return userWithClaims.getMySpecials();
}

public static class SimulationSaveResponse {
public final BioModel newBioModel;
public final Simulation newSimulation;
Expand Down
21 changes: 17 additions & 4 deletions vcell-api/src/main/java/org/vcell/rest/users/LoginFormRestlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,26 @@ public void handle(Request request, Response response) {
reference = new Reference(request.getResourceRef().getHostIdentifier()+"/"+VCellApiApplication.BIOMODEL);
}
String html = "<html>" +
"<style>\n" +
".content {\n" +
" margin: auto;\n" +
" width: 640px; \n" +
" padding: 50px;\n" +
" font-size: large;\n" +
" font-family: 'Lexend Deca', sans-serif; \n" +
" color: #2E475D; \n" +
"}\n" +
"</style>\n" +
"<body><div class=\"content\">\n" +
"<h3>Sign in to VCell</h3>" +
"<form name='login' action='"+"/"+VCellApiApplication.LOGIN+ "' method='post'>" +
"VCell userid <input type='text' name='"+VCellApiApplication.IDENTIFIER_FORMNAME+"' value=''/><br/>" +
"VCell password <input type='password' name='"+VCellApiApplication.SECRET_FORMNAME+"' value=''/><br/>" +
"userid<br/> <input type='text' name='"+VCellApiApplication.IDENTIFIER_FORMNAME+"' value=''/><br/> <br/>" +
"password<br/> <input type='password' name='"+VCellApiApplication.SECRET_FORMNAME+"' value=''/><br/>" +
"<input type='hidden' name='"+VCellApiApplication.REDIRECTURL_FORMNAME+"' value='"+reference+"'>" +
"<input type='submit' value='sign in'/>" +
"<br/> <br/><input type='submit' value='sign in'/>" +
"</form>" +
"</html>";
"</div></body>" +
"</html>";
response.setEntity(html, MediaType.TEXT_HTML);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public interface SimulationDatabase {

public Set<KeyValue> getUnreferencedSimulations() throws SQLException;

public User getUser(String username) throws DataAccessException, SQLException;
public User.SpecialUser getUser(String username) throws DataAccessException, SQLException;

public TreeMap<User.SPECIAL_CLAIM,TreeMap<User,String>> getSpecialUsers() throws DataAccessException, SQLException;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public class SimulationDatabaseDirect implements SimulationDatabase {
private AdminDBTopLevel adminDbTopLevel = null;
private DatabaseServerImpl databaseServerImpl = null;
private Map<KeyValue, FieldDataIdentifierSpec[]> simFieldDataIDMap = Collections.synchronizedMap(new HashMap<KeyValue, FieldDataIdentifierSpec[]>());
private Map<String, User> userMap = Collections.synchronizedMap(new HashMap<String, User>());
private Map<String, User.SpecialUser> userMap = Collections.synchronizedMap(new HashMap<>());
private SimpleJobStatusCache cache = null;

public static class SimJobStatusKey {
Expand Down Expand Up @@ -327,11 +327,11 @@ public Set<KeyValue> getUnreferencedSimulations() throws SQLException{
}

@Override
public User getUser(String username) throws DataAccessException, SQLException {
User user = null;
public User.SpecialUser getUser(String username) throws DataAccessException, SQLException {
User.SpecialUser user = null;

synchronized(userMap) {
user = (User)userMap.get(username);
user = userMap.get(username);
if (user!=null){
return user;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,8 @@ public void onDispatch(Simulation simulation, SimulationJobStatus simJobStatus,
public void onStartRequest(VCSimulationIdentifier vcSimID, User user, int simulationScanCount, SimulationDatabase simulationDatabase, VCMessageSession session, VCMessageSession dispatcherQueueSession) throws VCMessagingException, DataAccessException, SQLException {
KeyValue simKey = vcSimID.getSimulationKey();

boolean isAdmin = false;
User myUser = simulationDatabase.getUser(user.getName());
if(myUser instanceof User.SpecialUser) {
isAdmin = Arrays.asList(((User.SpecialUser)myUser).getMySpecials()).contains(User.SPECIAL_CLAIM.admins);
}
User.SpecialUser myUser = simulationDatabase.getUser(user.getName());
boolean isAdmin = Arrays.asList(myUser.getMySpecials()).contains(User.SPECIAL_CLAIM.admins);

SimulationInfo simulationInfo = null;
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -476,13 +476,9 @@ public synchronized void onDispatch(Simulation simulation, SimulationJobStatus o
//has checked the 'timeoutDisabledCheckBox' in SolverTaskDescriptionAdvancedPanel on the client-side GUI
boolean isPowerUser = simulation.getSolverTaskDescription().isTimeoutDisabled();//Set from GUI
if(isPowerUser) {//Check if user allowed to be power user for 'special1' long running sims (see User.SPECIALS and vc_specialusers table)
User myUser = simulationDatabase.getUser(simulation.getVersion().getOwner().getName());
if(myUser instanceof User.SpecialUser) {
//'special1' assigned to users by request to allow long running sims
isPowerUser = isPowerUser && Arrays.asList(((User.SpecialUser)myUser).getMySpecials()).contains(User.SPECIAL_CLAIM.powerUsers);
}else {
isPowerUser = false;
}
User.SpecialUser myUser = simulationDatabase.getUser(simulation.getVersion().getOwner().getName());
//'powerUsers' (previously called 'special1') assigned to users by request to allow long running sims
isPowerUser = isPowerUser && Arrays.asList(myUser.getMySpecials()).contains(User.SPECIAL_CLAIM.powerUsers);
}
SimulationTask simulationTask = new SimulationTask(new SimulationJob(simulation, jobIndex, fieldDataIdentifierSpecs), taskID,null,isPowerUser);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -722,7 +722,7 @@ public ApiClient getApiClient(String clientId, boolean bEnableRetry) throws SQLE



public User getUser(String userid, boolean bEnableRetry) throws DataAccessException, java.sql.SQLException {
public User.SpecialUser getUser(String userid, boolean bEnableRetry) throws DataAccessException, java.sql.SQLException {

Object lock = new Object();
Connection con = conFactory.getConnection(lock);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public SimulationJobStatusPersistent[] getSimulationJobStatus(boolean bActiveOnl
/**
* getUser method comment.
*/
public User getUser(String userid) throws DataAccessException {
public User.SpecialUser getUser(String userid) throws DataAccessException {
try {
return adminDbTop.getUser(userid,true);
}catch (Throwable e){
Expand Down

0 comments on commit c216b42

Please sign in to comment.