View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.restapi.v1;
20  
21  import java.io.IOException;
22  import java.time.ZoneOffset;
23  import java.time.ZonedDateTime;
24  import java.util.Date;
25  import java.util.Optional;
26  
27  import org.mycore.common.MCRSession;
28  import org.mycore.common.MCRSessionMgr;
29  import org.mycore.frontend.MCRFrontendUtil;
30  import org.mycore.frontend.jersey.MCRCacheControl;
31  import org.mycore.frontend.jersey.MCRJWTUtil;
32  import org.mycore.frontend.jersey.MCRJerseyUtil;
33  import org.mycore.frontend.jersey.access.MCRRequireLogin;
34  import org.mycore.frontend.jersey.filter.access.MCRRestrictedAccess;
35  import org.mycore.restapi.v1.utils.MCRRestAPIUtil;
36  
37  import com.auth0.jwt.JWT;
38  import com.auth0.jwt.exceptions.JWTVerificationException;
39  
40  import jakarta.servlet.http.HttpServletRequest;
41  import jakarta.ws.rs.DefaultValue;
42  import jakarta.ws.rs.GET;
43  import jakarta.ws.rs.HeaderParam;
44  import jakarta.ws.rs.NotAuthorizedException;
45  import jakarta.ws.rs.Path;
46  import jakarta.ws.rs.Produces;
47  import jakarta.ws.rs.core.Application;
48  import jakarta.ws.rs.core.Context;
49  import jakarta.ws.rs.core.Response;
50  
51  /**
52   * Rest Controller that handles authentication.
53   *
54   * @author Thomas Scheffler
55   * @author Robert Stephan
56   *
57   */
58  @Path("/auth")
59  public class MCRRestAPIAuthentication {
60  
61      private static final int EXPIRATION_TIME_MINUTES = 10;
62  
63      public static final String AUDIENCE = "mcr:rest-auth";
64  
65      @Context
66      HttpServletRequest req;
67  
68      @Context
69      Application app;
70  
71      /**
72       * Unauthenticated requests should return a response whose header contains a HTTP 401 Unauthorized status and a
73       * WWW-Authenticate field.
74       * 
75       * 200 OK Content-Type: application/json;charset=UTF-8
76       * 
77       * { "access_token": "NgCXRK...MzYjw", "token_type": "Bearer", "expires_at": 1372700873, "refresh_token":
78       * "NgAagA...Um_SHo" }
79       * 
80       * Returning the JWT (Java Web Token to the client is not properly specified). We use the "Authorization" Header in
81       * the response, which is unusual but not strictly forbidden.
82       * 
83       * @param authorization - content HTTP Header Authorization
84       * @throws IOException if JWT cannot be written
85       * @return response message as JSON
86       */
87      @GET
88      @Produces({ MCRJerseyUtil.APPLICATION_JSON_UTF8 })
89      @Path("/login")
90      @MCRCacheControl(noTransform = true,
91          noStore = true,
92          private_ = @MCRCacheControl.FieldArgument(active = true),
93          noCache = @MCRCacheControl.FieldArgument(active = true))
94      public Response authorize(@DefaultValue("") @HeaderParam("Authorization") String authorization) throws IOException {
95          if (authorization.startsWith("Basic ")) {
96              //login handled by MCRSessionFilter
97              Optional<String> jwt = getToken(MCRSessionMgr.getCurrentSession(),
98                  MCRFrontendUtil.getRemoteAddr(req));
99              if (jwt.isPresent()) {
100                 return MCRJWTUtil.getJWTLoginSuccessResponse(jwt.get());
101             }
102         }
103         throw new NotAuthorizedException(
104             "Login failed. Please provide proper user name and password via HTTP Basic Authentication.",
105             MCRRestAPIUtil.getWWWAuthenticateHeader("Basic", null, app));
106     }
107 
108     public static Optional<String> getToken(MCRSession session, String remoteIp) {
109         ZonedDateTime currentTime = ZonedDateTime.now(ZoneOffset.UTC);
110         return Optional.ofNullable(session)
111             .map(MCRJWTUtil::getJWTBuilder)
112             .map(b -> {
113                 return b.withAudience(AUDIENCE)
114                     .withClaim(MCRJWTUtil.JWT_CLAIM_IP, remoteIp)
115                     .withExpiresAt(Date.from(currentTime.plusMinutes(EXPIRATION_TIME_MINUTES).toInstant()))
116                     .withNotBefore(Date.from(currentTime.minusMinutes(EXPIRATION_TIME_MINUTES).toInstant()))
117                     .sign(MCRJWTUtil.getJWTAlgorithm());
118             });
119     }
120 
121     @GET
122     @Path("/renew")
123     @MCRRestrictedAccess(MCRRequireLogin.class)
124     @MCRCacheControl(noTransform = true,
125         noStore = true,
126         private_ = @MCRCacheControl.FieldArgument(active = true),
127         noCache = @MCRCacheControl.FieldArgument(active = true))
128     public Response renew(@DefaultValue("") @HeaderParam("Authorization") String authorization) throws IOException {
129         if (authorization.startsWith("Bearer ")) {
130             //login handled by MCRSessionFilter
131             Optional<String> jwt = getToken(MCRSessionMgr.getCurrentSession(),
132                 MCRFrontendUtil.getRemoteAddr(req));
133             if (jwt.isPresent()) {
134                 return MCRJWTUtil.getJWTRenewSuccessResponse(jwt.get());
135             }
136         }
137         throw new NotAuthorizedException(
138             "Login failed. Please provide a valid JSON Web Token for authentication.",
139             MCRRestAPIUtil.getWWWAuthenticateHeader("Basic", null, app));
140     }
141 
142     public static void validate(String token) throws JWTVerificationException {
143         JWT.require(MCRJWTUtil.getJWTAlgorithm())
144             .withAudience(AUDIENCE)
145             .acceptLeeway(0)
146             .build().verify(token);
147     }
148 }