-
Notifications
You must be signed in to change notification settings - Fork 500
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6974 from poikilotherm/6936-domaingroups
6936 Implement email domain based groups
- Loading branch information
Showing
21 changed files
with
1,182 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
Mail Domain Groups | ||
================== | ||
|
||
Groups can be defined based on the domain part of users (verified) email addresses. Email addresses that match | ||
one or more groups configuration will add the user to them. | ||
|
||
Within the scientific community, in many cases users will use a institutional email address for their account in a | ||
Dataverse installation. This might offer a simple solution for building groups of people, as the domain part can be | ||
seen as a selector for group membership. | ||
|
||
Some use cases: installations that like to avoid Shibboleth, enable self sign up, offer multi-tenancy or can't use | ||
:doc:`ip-groups` plus many more. | ||
|
||
.. hint:: Please be aware that non-verified mail addresses will exclude the user even if matching. This is to avoid | ||
privilege escalation. | ||
|
||
Listing Mail Domain Groups | ||
-------------------------- | ||
|
||
Mail Domain Groups can be listed with the following curl command: | ||
|
||
``curl http://localhost:8080/api/admin/groups/domain`` | ||
|
||
Listing a specific Mail Domain Group | ||
------------------------------------ | ||
|
||
Let's say you used "domainGroup1" as the alias of the Mail Domain Group you created below. | ||
To list just that Mail Domain Group, you can include the alias in the curl command like this: | ||
|
||
``curl http://localhost:8080/api/admin/groups/domain/domainGroup1`` | ||
|
||
|
||
Creating a Mail Domain Group | ||
---------------------------- | ||
|
||
Mail Domain Groups can be created with a simple JSON file: | ||
|
||
.. code-block:: json | ||
:caption: domainGroup1.json | ||
:name: domainGroup1.json | ||
{ | ||
"name": "Users from @example.org", | ||
"alias": "exampleorg", | ||
"description": "Any verified user from Example Org will be included in this group.", | ||
"domains": ["example.org"] | ||
} | ||
Giving a ``description`` is optional. The ``name`` will be visible in the permission UI, so be sure to pick a sensible | ||
value. | ||
|
||
The ``domains`` field is mandatory to be an array. This enables creation of multi-domain groups, too. | ||
|
||
Obviously you can create as many of these groups you might like, as long as the ``alias`` is unique. | ||
|
||
To load it into your Dataverse installation, either use a ``POST`` or ``PUT`` request (see below): | ||
|
||
``curl -X POST -H 'Content-type: application/json' http://localhost:8080/api/admin/groups/domain --upload-file domainGroup1.json`` | ||
|
||
Updating a Mail Domain Group | ||
---------------------------- | ||
|
||
Editing a group is done by replacing it. Grab your group definition like the :ref:`above example <domainGroup1.json>`, | ||
change it as you like and ``PUT`` it into your installation: | ||
|
||
``curl -X PUT -H 'Content-type: application/json' http://localhost:8080/api/admin/groups/domain/domainGroup1 --upload-file domainGroup1.json`` | ||
|
||
Please make sure that the alias of the group you want to change is included in the path. You also need to ensure | ||
that this alias matches with the one given in your JSON file. | ||
|
||
.. hint:: This is an idempotent call, so it will create the group given if not present. | ||
|
||
Deleting a Mail Domain Group | ||
---------------------------- | ||
|
||
To delete a Mail Domain Group with an alias of "domainGroup1", use the curl command below: | ||
|
||
``curl -X DELETE http://localhost:8080/api/admin/groups/domain/domainGroup1`` | ||
|
||
Please note: it is not recommended to delete a Mail Domain Group that has been assigned roles. If you want to delete | ||
a Mail Domain Group, you should first remove its permissions. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
64 changes: 64 additions & 0 deletions
64
.../java/edu/harvard/iq/dataverse/api/errorhandlers/ConstraintViolationExceptionHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package edu.harvard.iq.dataverse.api.errorhandlers; | ||
|
||
import edu.harvard.iq.dataverse.util.json.JsonPrinter; | ||
|
||
import javax.json.Json; | ||
import javax.json.JsonArray; | ||
import javax.json.JsonArrayBuilder; | ||
import javax.json.JsonObject; | ||
import javax.validation.ConstraintViolation; | ||
import javax.validation.ConstraintViolationException; | ||
import javax.ws.rs.core.MediaType; | ||
import javax.ws.rs.core.Response; | ||
import javax.ws.rs.ext.ExceptionMapper; | ||
import javax.ws.rs.ext.Provider; | ||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
|
||
@Provider | ||
public class ConstraintViolationExceptionHandler implements ExceptionMapper<ConstraintViolationException> { | ||
|
||
public class ValidationError { | ||
private String path; | ||
private String message; | ||
|
||
public String getPath() { return path; } | ||
public void setPath(String path) { this.path = path; } | ||
public String getMessage() { return message; } | ||
public void setMessage(String message) { this.message = message; } | ||
} | ||
|
||
@Override | ||
public Response toResponse(ConstraintViolationException exception) { | ||
|
||
List<ValidationError> errors = exception.getConstraintViolations().stream() | ||
.map(this::toValidationError) | ||
.collect(Collectors.toList()); | ||
|
||
return Response.status(Response.Status.BAD_REQUEST) | ||
.entity( Json.createObjectBuilder() | ||
.add("status", "ERROR") | ||
.add("code", Response.Status.BAD_REQUEST.getStatusCode()) | ||
.add("message", "JPA validation constraints failed persistence. See list of violations for details.") | ||
.add("violations", toJsonArray(errors)) | ||
.build()) | ||
.type(MediaType.APPLICATION_JSON_TYPE).build(); | ||
} | ||
|
||
private ValidationError toValidationError(ConstraintViolation constraintViolation) { | ||
ValidationError error = new ValidationError(); | ||
error.setPath(constraintViolation.getPropertyPath().toString()); | ||
error.setMessage(constraintViolation.getMessage()); | ||
return error; | ||
} | ||
|
||
private JsonArray toJsonArray(List<ValidationError> list) { | ||
JsonArrayBuilder builder = Json.createArrayBuilder(); | ||
list.stream() | ||
.forEach(error -> builder.add( | ||
Json.createObjectBuilder() | ||
.add("path", error.getPath()) | ||
.add("message", error.getMessage()))); | ||
return builder.build(); | ||
} | ||
} |
37 changes: 37 additions & 0 deletions
37
src/main/java/edu/harvard/iq/dataverse/api/errorhandlers/JsonParseExceptionHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package edu.harvard.iq.dataverse.api.errorhandlers; | ||
|
||
import edu.harvard.iq.dataverse.util.json.JsonParseException; | ||
|
||
import javax.json.Json; | ||
import javax.servlet.http.HttpServletRequest; | ||
import javax.ws.rs.BadRequestException; | ||
import javax.ws.rs.core.Context; | ||
import javax.ws.rs.core.MediaType; | ||
import javax.ws.rs.core.Response; | ||
import javax.ws.rs.ext.ExceptionMapper; | ||
import javax.ws.rs.ext.Provider; | ||
import java.util.UUID; | ||
import java.util.logging.Level; | ||
import java.util.logging.Logger; | ||
|
||
/** | ||
* Make a failing JSON parsing request appear to be a BadRequest (error code 400) | ||
* and send a message what just failed... | ||
*/ | ||
@Provider | ||
public class JsonParseExceptionHandler implements ExceptionMapper<JsonParseException>{ | ||
|
||
@Context | ||
HttpServletRequest request; | ||
|
||
@Override | ||
public Response toResponse(JsonParseException ex){ | ||
return Response.status(Response.Status.BAD_REQUEST) | ||
.entity( Json.createObjectBuilder() | ||
.add("status", "ERROR") | ||
.add("code", Response.Status.BAD_REQUEST.getStatusCode()) | ||
.add("message", ex.getMessage()) | ||
.build()) | ||
.type(MediaType.APPLICATION_JSON_TYPE).build(); | ||
} | ||
} |
70 changes: 70 additions & 0 deletions
70
src/main/java/edu/harvard/iq/dataverse/api/errorhandlers/ThrowableHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package edu.harvard.iq.dataverse.api.errorhandlers; | ||
|
||
import javax.annotation.Priority; | ||
import javax.json.Json; | ||
import javax.servlet.http.HttpServletRequest; | ||
import javax.ws.rs.core.Context; | ||
import javax.ws.rs.core.MediaType; | ||
import javax.ws.rs.core.Response; | ||
import javax.ws.rs.ext.ExceptionMapper; | ||
import javax.ws.rs.ext.Provider; | ||
import java.util.UUID; | ||
import java.util.logging.Level; | ||
import java.util.logging.Logger; | ||
|
||
/** | ||
* Produces a generic 500 message for the API, being a fallback handler for not specially treated exceptions. | ||
*/ | ||
@Provider | ||
public class ThrowableHandler implements ExceptionMapper<Throwable>{ | ||
|
||
private static final Logger logger = Logger.getLogger(ThrowableHandler.class.getName()); | ||
|
||
@Context | ||
HttpServletRequest request; | ||
|
||
@Override | ||
public Response toResponse(Throwable ex){ | ||
String incidentId = UUID.randomUUID().toString(); | ||
logger.log(Level.SEVERE, "Uncaught REST API exception:\n"+ | ||
" Incident: " + incidentId +"\n"+ | ||
" URL: "+getOriginalURL(request)+"\n"+ | ||
" Method: "+request.getMethod(), ex); | ||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR) | ||
.entity( Json.createObjectBuilder() | ||
.add("status", "ERROR") | ||
.add("code", Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()) | ||
.add("type", ex.getClass().getSimpleName()) | ||
.add("message", "Internal server error. More details available at the server logs.") | ||
.add("incidentId", incidentId) | ||
.build()) | ||
.type(MediaType.APPLICATION_JSON_TYPE).build(); | ||
} | ||
|
||
private String getOriginalURL(HttpServletRequest req) { | ||
// Rebuild the original request URL: http://stackoverflow.com/a/5212336/356408 | ||
String scheme = req.getScheme(); // http | ||
String serverName = req.getServerName(); // hostname.com | ||
int serverPort = req.getServerPort(); // 80 | ||
String contextPath = req.getContextPath(); // /mywebapp | ||
String servletPath = req.getServletPath(); // /servlet/MyServlet | ||
String pathInfo = req.getPathInfo(); // /a/b;c=123 | ||
String queryString = req.getQueryString(); // d=789 | ||
|
||
// Reconstruct original requesting URL | ||
StringBuilder url = new StringBuilder(); | ||
url.append(scheme).append("://").append(serverName); | ||
if (serverPort != 80 && serverPort != 443) { | ||
url.append(":").append(serverPort); | ||
} | ||
url.append(contextPath).append(servletPath); | ||
if (pathInfo != null) { | ||
url.append(pathInfo); | ||
} | ||
if (queryString != null) { | ||
url.append("?").append(queryString); | ||
} | ||
|
||
return url.toString(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.