From 39c4636d97b41fbc8b5d3027ecbf88cb1692f76a Mon Sep 17 00:00:00 2001 From: Rick Herrick <jrherrick@wustl.edu> Date: Mon, 30 May 2016 17:05:15 -0500 Subject: [PATCH] XNAT-4269 Fixed permissions error in UsersApi calls. Piggybacking fixes to add uptime calls to SiteConfigApi along with storing launch time in XnatAppInfo, replacing prearchive path call in PrearcUtils, removing stupid file, and changing the Configuration command on the Administer menu to Site Administration. --- .../nrg/xapi/rest/settings/SiteConfigApi.java | 33 +++++++ .../org/nrg/xapi/rest/users/UsersApi.java | 22 ++++- .../xnat/helpers/prearchive/PrearcUtils.java | 2 +- .../org/nrg/xnat/services/XnatAppInfo.java | 87 +++++++++++++++++-- src/main/webapp/pages/foo.jsp | 3 - .../screens/topBar/Administer/Default.vm | 2 +- 6 files changed, 136 insertions(+), 13 deletions(-) delete mode 100755 src/main/webapp/pages/foo.jsp diff --git a/src/main/java/org/nrg/xapi/rest/settings/SiteConfigApi.java b/src/main/java/org/nrg/xapi/rest/settings/SiteConfigApi.java index bd4957f1..72197588 100644 --- a/src/main/java/org/nrg/xapi/rest/settings/SiteConfigApi.java +++ b/src/main/java/org/nrg/xapi/rest/settings/SiteConfigApi.java @@ -44,6 +44,39 @@ public class SiteConfigApi extends AbstractXnatRestApi { return new ResponseEntity<>(_appInfo.getSystemProperties(), HttpStatus.OK); } + @ApiOperation(value = "Returns a map of extended build attributes.", notes = "The values are dependent on what attributes are set for the build. It is not unexpected that there are no extended build attributes.", response = String.class, responseContainer = "Map") + @ApiResponses({@ApiResponse(code = 200, message = "Extended build attributes successfully retrieved."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 500, message = "Unexpected error")}) + @RequestMapping(value = "buildInfo/attributes", produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET}) + public ResponseEntity<Map<String, Map<String, String>>> getBuildAttributeInfo() { + if (_log.isDebugEnabled()) { + _log.debug("User " + getSessionUser().getUsername() + " requested the extended application build attributes."); + } + + return new ResponseEntity<>(_appInfo.getSystemAttributes(), HttpStatus.OK); + } + + @ApiOperation(value = "Returns the system uptime.", notes = "This returns the uptime as a map of time units: days, hours, minutes, and seconds.", response = String.class, responseContainer = "Map") + @ApiResponses({@ApiResponse(code = 200, message = "System uptime successfully retrieved."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 500, message = "Unexpected error")}) + @RequestMapping(value = "uptime", produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET}) + public ResponseEntity<Map<String, String>> getSystemUptime() { + if (_log.isDebugEnabled()) { + _log.debug("User " + getSessionUser().getUsername() + " requested the system uptime map."); + } + + return new ResponseEntity<>(_appInfo.getUptime(), HttpStatus.OK); + } + + @ApiOperation(value = "Returns the system uptime.", notes = "This returns the uptime as a formatted string.", response = String.class) + @ApiResponses({@ApiResponse(code = 200, message = "System uptime successfully retrieved."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 500, message = "Unexpected error")}) + @RequestMapping(value = "uptime/display", produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET}) + public ResponseEntity<String> getFormattedSystemUptime() { + if (_log.isDebugEnabled()) { + _log.debug("User " + getSessionUser().getUsername() + " requested the formatted system uptime."); + } + + return new ResponseEntity<>(_appInfo.getFormattedUptime(), HttpStatus.OK); + } + @ApiOperation(value = "Returns the full map of site configuration properties.", notes = "Complex objects may be returned as encapsulated JSON strings.", response = String.class, responseContainer = "Map") @ApiResponses({@ApiResponse(code = 200, message = "Site configuration properties successfully retrieved."), @ApiResponse(code = 401, message = "Must be authenticated to access the XNAT REST API."), @ApiResponse(code = 403, message = "Not authorized to set site configuration properties."), @ApiResponse(code = 500, message = "Unexpected error")}) @RequestMapping(produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET}) diff --git a/src/main/java/org/nrg/xapi/rest/users/UsersApi.java b/src/main/java/org/nrg/xapi/rest/users/UsersApi.java index 68728939..2f98f6cc 100644 --- a/src/main/java/org/nrg/xapi/rest/users/UsersApi.java +++ b/src/main/java/org/nrg/xapi/rest/users/UsersApi.java @@ -6,6 +6,7 @@ import org.nrg.framework.annotations.XapiRestController; import org.nrg.xapi.model.users.User; import org.nrg.xapi.rest.NotFoundException; import org.nrg.xdat.XDAT; +import org.nrg.xdat.preferences.SiteConfigPreferences; import org.nrg.xdat.rest.AbstractXnatRestApi; import org.nrg.xdat.security.UserGroupI; import org.nrg.xdat.security.helpers.Groups; @@ -19,6 +20,8 @@ import org.nrg.xft.event.EventUtils; import org.nrg.xft.security.UserI; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -37,14 +40,26 @@ public class UsersApi extends AbstractXnatRestApi { @RequestMapping(produces = {MediaType.APPLICATION_JSON_VALUE}, method = RequestMethod.GET) @ResponseBody public ResponseEntity<List<String>> usersGet() { + if (_preferences.getRestrictUserListAccessToAdmins()) { + final HttpStatus status = isPermitted(); + if (status != null) { + return new ResponseEntity<>(status); + } + } return new ResponseEntity<List<String>>(new ArrayList<>(Users.getAllLogins()), HttpStatus.OK); } @ApiOperation(value = "Get list of user profiles.", notes = "The users' profiles function returns a list of all users of the XNAT system with brief information about each.", response = User.class, responseContainer = "List") @ApiResponses({@ApiResponse(code = 200, message = "An array of user profiles"), @ApiResponse(code = 500, message = "Unexpected error")}) - @RequestMapping(value = {"/profiles"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = RequestMethod.GET) + @RequestMapping(value = {"profiles"}, produces = {MediaType.APPLICATION_JSON_VALUE}, method = RequestMethod.GET) @ResponseBody public ResponseEntity<List<Map<String, String>>> usersProfilesGet() { + if (_preferences.getRestrictUserListAccessToAdmins()) { + final HttpStatus status = isPermitted(); + if (status != null) { + return new ResponseEntity<>(status); + } + } List<UserI> users = Users.getUsers(); List<Map<String, String>> userMaps = new ArrayList<>(); for (UserI user : users) { @@ -396,7 +411,6 @@ public class UsersApi extends AbstractXnatRestApi { } } - @SuppressWarnings("unused") public static class Event { public static String Added = "Added User"; @@ -410,4 +424,8 @@ public class UsersApi extends AbstractXnatRestApi { public static String ModifiedSettings = "Modified User Settings"; public static String VerifiedEmail = "Verified User Email"; } + + @Autowired + @Lazy + private SiteConfigPreferences _preferences; } diff --git a/src/main/java/org/nrg/xnat/helpers/prearchive/PrearcUtils.java b/src/main/java/org/nrg/xnat/helpers/prearchive/PrearcUtils.java index a260cf18..befae30b 100644 --- a/src/main/java/org/nrg/xnat/helpers/prearchive/PrearcUtils.java +++ b/src/main/java/org/nrg/xnat/helpers/prearchive/PrearcUtils.java @@ -215,7 +215,7 @@ public class PrearcUtils { String prearcPath; if (project == null || project.equals(COMMON)) { if (allowUnassigned || user == null || Roles.isSiteAdmin(user)) { - prearcPath = ArcSpecManager.GetInstance(false).getGlobalPrearchivePath(); + prearcPath = XDAT.getSiteConfigPreferences().getPrearchivePath(); } else { throw new InvalidPermissionException("user " + user.getUsername() + " does not have permission to access the Unassigned directory "); } diff --git a/src/main/java/org/nrg/xnat/services/XnatAppInfo.java b/src/main/java/org/nrg/xnat/services/XnatAppInfo.java index e6f2fd79..0589dacc 100644 --- a/src/main/java/org/nrg/xnat/services/XnatAppInfo.java +++ b/src/main/java/org/nrg/xnat/services/XnatAppInfo.java @@ -10,15 +10,26 @@ import javax.inject.Inject; import javax.servlet.ServletContext; import java.io.IOException; import java.io.InputStream; +import java.text.DecimalFormat; import java.util.*; import java.util.jar.Attributes; import java.util.jar.Manifest; @Component public class XnatAppInfo { + + public static final int MILLISECONDS_IN_A_DAY = (24 * 60 * 60 * 1000); + public static final int MILLISECONDS_IN_AN_HOUR = (60 * 60 * 1000); + public static final int MILLISECONDS_IN_A_MINUTE = (60 * 1000); + public static final DecimalFormat SECONDS_FORMAT = new DecimalFormat("##.000"); + public static final String DAYS = "days"; + public static final String HOURS = "hours"; + public static final String MINUTES = "minutes"; + public static final String SECONDS = "seconds"; + public XnatAppInfo(final ServletContext context) throws IOException { try (final InputStream input = context.getResourceAsStream("/META-INF/MANIFEST.MF")) { - final Manifest manifest = new Manifest(input); + final Manifest manifest = new Manifest(input); final Attributes attributes = manifest.getMainAttributes(); _properties.setProperty("buildNumber", attributes.getValue("Build-Number")); _properties.setProperty("buildDate", attributes.getValue("Build-Date")); @@ -52,6 +63,7 @@ public class XnatAppInfo { /** * Indicates whether the XNAT system has been initialized yet. + * * @return Returns true if the system has been initialized, false otherwise. */ public boolean isInitialized() { @@ -72,10 +84,10 @@ public class XnatAppInfo { * properties are guaranteed to include the following: * * <ul> - * <li>version</li> - * <li>buildNumber</li> - * <li>buildDate</li> - * <li>commit</li> + * <li>version</li> + * <li>buildNumber</li> + * <li>buildDate</li> + * <li>commit</li> * </ul> * * There may be other properties available in the system properties and even more available through the {@link @@ -84,7 +96,7 @@ public class XnatAppInfo { * @return The primary system properties. */ public Properties getSystemProperties() { - return (Properties)_properties.clone(); + return (Properties) _properties.clone(); } /** @@ -96,6 +108,68 @@ public class XnatAppInfo { return new HashMap<>(_attributes); } + /** + * Returns the date indicating the time the system was last started. + * + * @return A date representing the last start time. + */ + public Date getStartTime() { + return new Date(_startTime.getTime()); + } + + /** + * Returns the system uptime as a map of strings indicating the number of days, hours, minutes, and seconds since + * the system was last restarted. The map keys are {@link #DAYS}, {@link #HOURS}, {@link #MINUTES}, and {@link + * #SECONDS}. You can use these values when creating a custom display with the uptime values. If you want a simple + * string with the uptime already formatted, you can use {@link #getFormattedUptime()} instead. + * + * @return A map of values indicating the system uptime. + */ + public Map<String, String> getUptime() { + final long diff = new Date().getTime() - _startTime.getTime(); + final int days = (int) (diff / MILLISECONDS_IN_A_DAY); + final long daysRemainder = diff % MILLISECONDS_IN_A_DAY; + final int hours = (int) (daysRemainder / MILLISECONDS_IN_AN_HOUR); + final long hoursRemainder = daysRemainder % MILLISECONDS_IN_AN_HOUR; + final int minutes = (int) (hoursRemainder / MILLISECONDS_IN_A_MINUTE); + final long minutesRemainder = hoursRemainder % MILLISECONDS_IN_A_MINUTE; + + final Map<String, String> uptime = new HashMap<>(); + if (days > 0) { + uptime.put(DAYS, Integer.toString(days)); + } + if (hours > 0) { + uptime.put(HOURS, Integer.toString(hours)); + } + if (minutes > 0) { + uptime.put(MINUTES, Integer.toString(minutes)); + } + uptime.put(SECONDS, SECONDS_FORMAT.format(minutesRemainder / 1000F)); + + return uptime; + } + + /** + * Returns the system uptime in a formatted display string. + * + * @return The formatted system uptime. + */ + public String getFormattedUptime() { + final Map<String, String> uptime = getUptime(); + final StringBuilder buffer = new StringBuilder(); + if (uptime.containsKey(DAYS)) { + buffer.append(uptime.get(DAYS)).append(" days, "); + } + if (uptime.containsKey(HOURS)) { + buffer.append(uptime.get(HOURS)).append(" hours, "); + } + if (uptime.containsKey(MINUTES)) { + buffer.append(uptime.get(MINUTES)).append(" minutes, "); + } + buffer.append(uptime.get(SECONDS)).append(" seconds"); + return buffer.toString(); + } + private static final Logger _log = LoggerFactory.getLogger(XnatAppInfo.class); private static final List<String> PRIMARY_MANIFEST_ATTRIBUTES = Arrays.asList("Build-Number", "Build-Date", "Implementation-Version", "Implementation-Sha"); @@ -103,6 +177,7 @@ public class XnatAppInfo { @Inject private JdbcTemplate _template; + private final Date _startTime = new Date(); private final Properties _properties = new Properties(); private final Map<String, Map<String, String>> _attributes = new HashMap<>(); private boolean _initialized = false; diff --git a/src/main/webapp/pages/foo.jsp b/src/main/webapp/pages/foo.jsp deleted file mode 100755 index b8805354..00000000 --- a/src/main/webapp/pages/foo.jsp +++ /dev/null @@ -1,3 +0,0 @@ -<%@ page contentType="text/html;charset=UTF-8" language="java" %> - -<h1>Foo</h1> diff --git a/src/main/webapp/xnat-templates/screens/topBar/Administer/Default.vm b/src/main/webapp/xnat-templates/screens/topBar/Administer/Default.vm index 0fe8d13b..5792b19d 100644 --- a/src/main/webapp/xnat-templates/screens/topBar/Administer/Default.vm +++ b/src/main/webapp/xnat-templates/screens/topBar/Administer/Default.vm @@ -1,9 +1,9 @@ <!-- Sequence: 10 --> +<li><a href="/page/admin/">Site Administration</a></li> <li><a href="$link.setPage("XDATScreen_admin.vm")">Users</a></li> <li><a href="$link.setPage("XDATScreen_groups.vm")">Groups</a></li> <li><a href="$link.setPage("XDATScreen_dataTypes.vm")">Data Types</a></li> <li><a href="$link.setPage("XDATScreen_email.vm")">Email</a></li> <li><a href="$link.setPage("XDATScreen_manage_pipeline.vm")">Pipelines</a></li> -<li><a href="/page/admin/">Configuration</a></li> <li><a href="$link.setPage("Scripts.vm")">Automation</a></li> <li><a href="$link.setPage("XDATScreen_admin_options.vm")">More...</a></li> -- GitLab