1 | package clarin.cmdi.componentregistry.rest; |
---|
2 | |
---|
3 | import clarin.cmdi.componentregistry.AuthenticationRequiredException; |
---|
4 | import clarin.cmdi.componentregistry.ComponentRegistryFactory; |
---|
5 | import clarin.cmdi.componentregistry.UserCredentials; |
---|
6 | import clarin.cmdi.componentregistry.impl.database.ValidationException; |
---|
7 | import clarin.cmdi.componentregistry.model.AuthenticationInfo; |
---|
8 | import com.google.common.base.Strings; |
---|
9 | import com.wordnik.swagger.annotations.Api; |
---|
10 | import com.wordnik.swagger.annotations.ApiOperation; |
---|
11 | import com.wordnik.swagger.annotations.ApiResponse; |
---|
12 | import com.wordnik.swagger.annotations.ApiResponses; |
---|
13 | import java.net.URI; |
---|
14 | import java.security.Principal; |
---|
15 | import javax.ws.rs.DefaultValue; |
---|
16 | import javax.ws.rs.GET; |
---|
17 | import javax.ws.rs.POST; |
---|
18 | import javax.ws.rs.Path; |
---|
19 | import javax.ws.rs.Produces; |
---|
20 | import javax.ws.rs.QueryParam; |
---|
21 | import javax.ws.rs.core.Context; |
---|
22 | import javax.ws.rs.core.MediaType; |
---|
23 | import javax.ws.rs.core.Response; |
---|
24 | import javax.ws.rs.core.SecurityContext; |
---|
25 | import javax.ws.rs.core.UriInfo; |
---|
26 | import org.json.JSONException; |
---|
27 | import org.slf4j.Logger; |
---|
28 | import org.slf4j.LoggerFactory; |
---|
29 | import org.springframework.stereotype.Service; |
---|
30 | import org.springframework.transaction.annotation.Transactional; |
---|
31 | |
---|
32 | /** |
---|
33 | * Authentication resource to be used by the client to retrieve the current |
---|
34 | * authentication status and/or to force an authentication request if the user |
---|
35 | * is not authenticated. |
---|
36 | * |
---|
37 | * <p> |
---|
38 | * A 'GET' on this resource will return a JSON or XML structure with the |
---|
39 | * following information:</p> |
---|
40 | * <ul> |
---|
41 | * <li>authentication (true/false)</li> |
---|
42 | * <li>username (string)</li> |
---|
43 | * <li>displayName</li> |
---|
44 | * (string) |
---|
45 | * </ul> |
---|
46 | * |
---|
47 | * <p> |
---|
48 | * A 'POST' to this resource will trigger an authentication request (by means of |
---|
49 | * a 401) response code if the user is not yet authenticated. In case of a |
---|
50 | * successful authentication, it will respond with a redirect (303) to this same |
---|
51 | * resource.</p> |
---|
52 | * |
---|
53 | * <p> |
---|
54 | * A query parameter 'redirect' is accepted on the GET. If it is present, the |
---|
55 | * service will respond with a redirect to the provided URI. This way, the |
---|
56 | * client can make sure that the user is lead back to the front end in the |
---|
57 | * desired state. Passing the 'redirect' query parameter in the POST response |
---|
58 | * will cause it to be preserved in the redirect to the GET. To execute a |
---|
59 | * 'login' action, a front end application will therefore typically send a POST |
---|
60 | * to {@code <SERVICE_BASE_URI>/authentication?redirect=<FRONT_END_URI>}.</p> |
---|
61 | * |
---|
62 | * @author Twan Goosen <twan.goosen@mpi.nl> |
---|
63 | */ |
---|
64 | @Path("/authentication") |
---|
65 | @Service |
---|
66 | @Transactional(rollbackFor = {Exception.class, ValidationException.class}) |
---|
67 | @Api(value = "/authentication", description = "REST resource for handling the authentication status", produces = MediaType.APPLICATION_XML) |
---|
68 | public class AuthenticationRestService { |
---|
69 | |
---|
70 | private final Logger logger = LoggerFactory.getLogger(AuthenticationRestService.class); |
---|
71 | |
---|
72 | @Context |
---|
73 | private SecurityContext security; |
---|
74 | @Context |
---|
75 | private UriInfo uriInfo; |
---|
76 | |
---|
77 | @GET |
---|
78 | @Produces({MediaType.TEXT_XML, MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) |
---|
79 | @ApiOperation(value = "Information on the current authentication state. Pass 'redirect' query parameter to make this method redirect to the URI specified as its value.") |
---|
80 | @ApiResponses(value = { |
---|
81 | @ApiResponse(code = 200, message = "If no query parameters are passed, with the authentications status in its body"), |
---|
82 | @ApiResponse(code = 303, message = "A redirect to the URI provided as the value of the 'redirect' parameter") |
---|
83 | }) |
---|
84 | public Response getAuthenticationInformation(@QueryParam("redirect") @DefaultValue("") String redirectUri) throws JSONException, AuthenticationRequiredException { |
---|
85 | final Principal userPrincipal = security.getUserPrincipal(); |
---|
86 | |
---|
87 | final AuthenticationInfo authInfo; |
---|
88 | if (userPrincipal == null || ComponentRegistryFactory.ANONYMOUS_USER.equals(userPrincipal.getName())) { |
---|
89 | authInfo = new AuthenticationInfo(false); |
---|
90 | } else { |
---|
91 | authInfo = new AuthenticationInfo(new UserCredentials(userPrincipal)); |
---|
92 | } |
---|
93 | |
---|
94 | if (Strings.isNullOrEmpty(redirectUri)) { |
---|
95 | return Response.ok(authInfo).build(); |
---|
96 | } else { |
---|
97 | return Response.seeOther(URI.create(redirectUri)).entity(authInfo).build(); |
---|
98 | } |
---|
99 | } |
---|
100 | |
---|
101 | @POST |
---|
102 | @ApiOperation(value = "Triggers the service to require the client to authenticate by means of the configured authentication mechanism. Notice that this might require user interaction!") |
---|
103 | @ApiResponses(value = { |
---|
104 | @ApiResponse(code = 303, message = "A redirect, either to a Shibboleth authentication page/discovery service or other identification mechanism, and ultimately to the same URI as requested (which should be picked up as a GET)"), |
---|
105 | @ApiResponse(code = 401, message = "If unauthenticated, a request to authenticate may be returned (not in case of Shibboleth authentication)") |
---|
106 | }) |
---|
107 | public Response triggerAuthenticationRequest() { |
---|
108 | logger.debug("Client has triggered authentication request"); |
---|
109 | |
---|
110 | //done - redirect to GET |
---|
111 | return Response.seeOther(uriInfo.getRequestUri()).build(); |
---|
112 | } |
---|
113 | } |
---|