blob: 141e8ecb4112c6ffe1579d4385ba022a576f401b [file] [log] [blame]
margarethaec247dd2018-06-12 21:55:46 +02001package de.ids_mannheim.korap.web.controller;
2
margarethab36b1a32018-06-20 20:13:07 +02003import java.net.MalformedURLException;
margarethaec247dd2018-06-12 21:55:46 +02004import java.net.URI;
margarethab36b1a32018-06-20 20:13:07 +02005import java.net.URL;
margarethaa2ce63d2018-06-28 10:11:43 +02006import java.time.ZonedDateTime;
margarethaec247dd2018-06-12 21:55:46 +02007import java.util.Map;
8
9import javax.servlet.http.HttpServletRequest;
10import javax.ws.rs.Consumes;
margaretha19295962018-06-26 16:00:47 +020011import javax.ws.rs.GET;
margarethaec247dd2018-06-12 21:55:46 +020012import javax.ws.rs.POST;
13import javax.ws.rs.Path;
14import javax.ws.rs.Produces;
15import javax.ws.rs.core.Context;
16import javax.ws.rs.core.MediaType;
17import javax.ws.rs.core.MultivaluedMap;
18import javax.ws.rs.core.Response;
19import javax.ws.rs.core.Response.ResponseBuilder;
margarethab36b1a32018-06-20 20:13:07 +020020import javax.ws.rs.core.Response.Status;
margarethaec247dd2018-06-12 21:55:46 +020021import javax.ws.rs.core.SecurityContext;
22
23import org.springframework.beans.factory.annotation.Autowired;
24import org.springframework.stereotype.Controller;
25
margarethab36b1a32018-06-20 20:13:07 +020026import com.nimbusds.oauth2.sdk.AccessTokenResponse;
margarethaec247dd2018-06-12 21:55:46 +020027import com.nimbusds.oauth2.sdk.ParseException;
margarethada3c7852018-06-14 20:35:11 +020028import com.nimbusds.oauth2.sdk.ResponseMode;
margarethab36b1a32018-06-20 20:13:07 +020029import com.nimbusds.oauth2.sdk.TokenRequest;
30import com.nimbusds.oauth2.sdk.http.HTTPRequest.Method;
margarethada3c7852018-06-14 20:35:11 +020031import com.nimbusds.oauth2.sdk.id.State;
margarethaec247dd2018-06-12 21:55:46 +020032import com.sun.jersey.spi.container.ResourceFilters;
33
34import de.ids_mannheim.korap.exceptions.KustvaktException;
margaretha9c78e1a2018-06-27 14:12:35 +020035import de.ids_mannheim.korap.oauth2.openid.OpenIdConfiguration;
margarethab36b1a32018-06-20 20:13:07 +020036import de.ids_mannheim.korap.oauth2.openid.OpenIdHttpRequestWrapper;
margaretha19295962018-06-26 16:00:47 +020037import de.ids_mannheim.korap.oauth2.openid.service.JWKService;
margarethaec247dd2018-06-12 21:55:46 +020038import de.ids_mannheim.korap.oauth2.openid.service.OpenIdAuthorizationService;
margaretha9c78e1a2018-06-27 14:12:35 +020039import de.ids_mannheim.korap.oauth2.openid.service.OpenIdConfigService;
margarethab36b1a32018-06-20 20:13:07 +020040import de.ids_mannheim.korap.oauth2.openid.service.OpenIdTokenService;
margarethaec247dd2018-06-12 21:55:46 +020041import de.ids_mannheim.korap.security.context.TokenContext;
margarethada3c7852018-06-14 20:35:11 +020042import de.ids_mannheim.korap.web.OpenIdResponseHandler;
margarethaec247dd2018-06-12 21:55:46 +020043import de.ids_mannheim.korap.web.filter.AuthenticationFilter;
44import de.ids_mannheim.korap.web.filter.BlockingFilter;
45import de.ids_mannheim.korap.web.utils.MapUtils;
46
47@Controller
48@Path("/oauth2/openid")
49public class OAuth2WithOpenIdController {
50
51 @Autowired
52 private OpenIdAuthorizationService authzService;
margarethada3c7852018-06-14 20:35:11 +020053 @Autowired
margarethab36b1a32018-06-20 20:13:07 +020054 private OpenIdTokenService tokenService;
55 @Autowired
margaretha19295962018-06-26 16:00:47 +020056 private JWKService jwkService;
57 @Autowired
margaretha9c78e1a2018-06-27 14:12:35 +020058 private OpenIdConfigService configService;
margarethaa2ce63d2018-06-28 10:11:43 +020059
margaretha9c78e1a2018-06-27 14:12:35 +020060 @Autowired
margarethada3c7852018-06-14 20:35:11 +020061 private OpenIdResponseHandler openIdResponseHandler;
margarethaec247dd2018-06-12 21:55:46 +020062
63 /**
64 * Required parameters for OpenID authentication requests:
65 *
66 * <ul>
67 * <li>scope: MUST contain "openid" for OpenID Connect
margarethaa2ce63d2018-06-28 10:11:43 +020068 * requests</li>
69 * <li>response_type: only "code" is supported</li>
70 * <li>client_id: client identifier given by Kustvakt during
71 * client registration</li>
margarethaec247dd2018-06-12 21:55:46 +020072 * <li>redirect_uri: MUST match a pre-registered redirect uri
margarethaa2ce63d2018-06-28 10:11:43 +020073 * during client registration</li>
margarethaec247dd2018-06-12 21:55:46 +020074 * </ul>
75 *
76 * Other parameters:
77 *
78 * <ul>
margarethada3c7852018-06-14 20:35:11 +020079 * <li>state (recommended): Opaque value used to maintain state
80 * between the request and the callback.</li>
81 * <li>response_mode (optional) : mechanism to be used for
margarethaa2ce63d2018-06-28 10:11:43 +020082 * returning parameters, only "query" is supported</li>
margarethada3c7852018-06-14 20:35:11 +020083 * <li>nonce (optional): String value used to associate a Client
84 * session with an ID Token,
margarethaec247dd2018-06-12 21:55:46 +020085 * and to mitigate replay attacks. </li>
margarethada3c7852018-06-14 20:35:11 +020086 * <li>display (optional): specifies how the Authorization Server
87 * displays the authentication and consent user interface
88 * pages. Options: page (default), popup, touch, wap. This
89 * parameter is more relevant for Kalamar. </li>
90 * <li>prompt (optional): specifies if the Authorization Server
91 * prompts the End-User for reauthentication and consent. Defined
92 * values: none, login, consent, select_account </li>
margarethaec247dd2018-06-12 21:55:46 +020093 * <li>max_age (optional): maximum Authentication Age.</li>
margarethada3c7852018-06-14 20:35:11 +020094 * <li>ui_locales (optional): preferred languages and scripts for
95 * the user interface represented as a space-separated list of
96 * BCP47 [RFC5646] </li>
97 * <li>id_token_hint (optional): ID Token previously issued by the
98 * Authorization Server being passed as a hint</li>
99 * <li>login_hint (optional): hint to the Authorization Server
100 * about the login identifier the End-User might use to log
101 * in</li>
102 * <li>acr_values (optional): requested Authentication Context
103 * Class Reference values. </li>
margarethaec247dd2018-06-12 21:55:46 +0200104 * </ul>
105 *
margaretha19295962018-06-26 16:00:47 +0200106 * @see "OpenID Connect Core 1.0 specification"
margarethaec247dd2018-06-12 21:55:46 +0200107 *
108 * @param request
109 * @param context
110 * @param form
111 * @return a redirect to client redirect uri
112 */
113 @POST
114 @Path("authorize")
115 @ResourceFilters({ AuthenticationFilter.class, BlockingFilter.class })
116 @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
117 @Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
118 public Response requestAuthorizationCode (
119 @Context HttpServletRequest request,
120 @Context SecurityContext context,
121 MultivaluedMap<String, String> form) {
122
margarethaec247dd2018-06-12 21:55:46 +0200123 TokenContext tokenContext = (TokenContext) context.getUserPrincipal();
124 String username = tokenContext.getUsername();
margarethaa2ce63d2018-06-28 10:11:43 +0200125 ZonedDateTime authTime = tokenContext.getAuthenticationTime();
margarethaec247dd2018-06-12 21:55:46 +0200126
margarethada3c7852018-06-14 20:35:11 +0200127 Map<String, String> map = MapUtils.toMap(form);
128 State state = authzService.retrieveState(map);
129 ResponseMode responseMode = authzService.retrieveResponseMode(map);
130
131 boolean isAuthentication = false;
132 if (map.containsKey("scope") && map.get("scope").contains("openid")) {
133 isAuthentication = true;
134 }
135
margarethaec247dd2018-06-12 21:55:46 +0200136 URI uri = null;
137 try {
margarethada3c7852018-06-14 20:35:11 +0200138 if (isAuthentication) {
139 authzService.checkRedirectUriParam(map);
140 }
141 uri = authzService.requestAuthorizationCode(map, username,
margarethaa2ce63d2018-06-28 10:11:43 +0200142 isAuthentication, authTime);
margarethada3c7852018-06-14 20:35:11 +0200143 }
144 catch (ParseException e) {
margaretha249a0aa2018-06-28 22:25:14 +0200145 return openIdResponseHandler.createErrorResponse(e, state);
margarethaec247dd2018-06-12 21:55:46 +0200146 }
147 catch (KustvaktException e) {
margarethada3c7852018-06-14 20:35:11 +0200148 return openIdResponseHandler.createAuthorizationErrorResponse(e,
149 isAuthentication, e.getRedirectUri(), state, responseMode);
margarethaec247dd2018-06-12 21:55:46 +0200150 }
margarethada3c7852018-06-14 20:35:11 +0200151
margarethaec247dd2018-06-12 21:55:46 +0200152 ResponseBuilder builder = Response.temporaryRedirect(uri);
153 return builder.build();
154 }
margarethada3c7852018-06-14 20:35:11 +0200155
margarethab36b1a32018-06-20 20:13:07 +0200156
157 @POST
158 @Path("token")
159 @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
160 @Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
161 public Response requestAccessToken (
162 @Context HttpServletRequest servletRequest,
163 MultivaluedMap<String, String> form) {
164
165 Map<String, String> map = MapUtils.toMap(form);
166 Method method = Method.valueOf(servletRequest.getMethod());
167 URL url = null;
168 try {
169 url = new URL(servletRequest.getRequestURL().toString());
170 }
171 catch (MalformedURLException e) {
172 // TODO Auto-generated catch block
173 e.printStackTrace();
174 }
175
176 try {
177 OpenIdHttpRequestWrapper httpRequest =
178 new OpenIdHttpRequestWrapper(method, url);
179 httpRequest.toHttpRequest(servletRequest, map);
180
181 TokenRequest tokenRequest = TokenRequest.parse(httpRequest);
182 AccessTokenResponse tokenResponse =
183 tokenService.requestAccessToken(tokenRequest);
184 return openIdResponseHandler.createResponse(tokenResponse,
185 Status.OK);
186 }
187 catch (ParseException e) {
margaretha249a0aa2018-06-28 22:25:14 +0200188 return openIdResponseHandler.createErrorResponse(e, null);
margarethab36b1a32018-06-20 20:13:07 +0200189 }
190 catch (KustvaktException e) {
margaretha249a0aa2018-06-28 22:25:14 +0200191 return openIdResponseHandler.createTokenErrorResponse(e);
margarethab36b1a32018-06-20 20:13:07 +0200192 }
margarethab36b1a32018-06-20 20:13:07 +0200193 }
margaretha19295962018-06-26 16:00:47 +0200194
195 /**
196 * Retrieves Kustvakt public keys of JWK (Json Web Key) set
197 * format.
198 *
199 * @return json string representation of the public keys
200 *
201 * @see "RFC 8017 regarding RSA specifications"
202 * @see "RFC 7517 regarding JWK (Json Web Key) and JWK Set"
203 */
204 @GET
margaretha9c78e1a2018-06-27 14:12:35 +0200205 @Path("jwks")
margaretha19295962018-06-26 16:00:47 +0200206 @Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
margaretha9c78e1a2018-06-27 14:12:35 +0200207 public String requestPublicKeys () {
margaretha19295962018-06-26 16:00:47 +0200208 return jwkService.generatePublicKeySetJson();
209 }
margaretha9c78e1a2018-06-27 14:12:35 +0200210
211 /**
212 * When supporting discovery, must be available at
213 * {issuer_uri}/.well-known/openid-configuration
margarethaa2ce63d2018-06-28 10:11:43 +0200214 *
215 * @return
margaretha9c78e1a2018-06-27 14:12:35 +0200216 *
217 * @return
218 */
219 @GET
220 @Path("config")
221 @Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
222 public OpenIdConfiguration requestOpenIdConfig () {
223 return configService.retrieveOpenIdConfigInfo();
224 }
margarethaec247dd2018-06-12 21:55:46 +0200225}