blob: bc4f0c966f1e8a63e72f2f2dbf6fd3b313b6b3a5 [file] [log] [blame]
margarethad3c0fc92017-10-25 15:03:32 +02001package de.ids_mannheim.korap.web.controller;
Michael Hanlfb839b92015-09-19 21:32:34 +02002
margaretha894a7d72017-11-08 19:24:20 +01003import java.util.HashMap;
4import java.util.Iterator; // 07.02.17/FB
5import java.util.List;
6import java.util.Locale;
7import java.util.Map;
8
9import javax.ws.rs.Consumes;
10import javax.ws.rs.GET;
11import javax.ws.rs.HeaderParam;
12import javax.ws.rs.POST;
13import javax.ws.rs.Path;
14import javax.ws.rs.Produces;
15import javax.ws.rs.QueryParam;
16import javax.ws.rs.core.Context;
17import javax.ws.rs.core.HttpHeaders;
18import javax.ws.rs.core.MediaType;
19import javax.ws.rs.core.MultivaluedMap;
20import javax.ws.rs.core.Response;
21import javax.ws.rs.core.SecurityContext;
22
23import org.slf4j.Logger;
24import org.springframework.beans.factory.annotation.Autowired;
25import org.springframework.stereotype.Controller;
26
Michael Hanlfb839b92015-09-19 21:32:34 +020027import com.sun.jersey.spi.container.ContainerRequest;
28import com.sun.jersey.spi.container.ResourceFilters;
Bodmo3d6bd352017-04-25 11:31:39 +020029
margaretha4b5c1412017-11-15 20:55:04 +010030import de.ids_mannheim.korap.authentication.framework.AuthorizationData;
31import de.ids_mannheim.korap.authentication.framework.HttpAuthorizationHandler;
32import de.ids_mannheim.korap.authentication.framework.TransferEncoding;
Michael Hanl00b64e02016-05-24 20:24:27 +020033import de.ids_mannheim.korap.config.Attributes;
margaretha139d0f72017-11-14 18:56:22 +010034import de.ids_mannheim.korap.config.AuthenticationType;
Michael Hanldaf86602016-05-12 14:31:52 +020035import de.ids_mannheim.korap.config.BeansFactory;
Michael Hanlfb839b92015-09-19 21:32:34 +020036import de.ids_mannheim.korap.exceptions.KustvaktException;
37import de.ids_mannheim.korap.exceptions.StatusCodes;
38import de.ids_mannheim.korap.interfaces.AuthenticationManagerIface;
margaretha894a7d72017-11-08 19:24:20 +010039import de.ids_mannheim.korap.user.TokenContext;
40import de.ids_mannheim.korap.user.User;
Michael Hanlfb839b92015-09-19 21:32:34 +020041import de.ids_mannheim.korap.utils.JsonUtils;
42import de.ids_mannheim.korap.utils.KustvaktLogger;
Michael Hanldaf86602016-05-12 14:31:52 +020043import de.ids_mannheim.korap.utils.ServiceInfo;
margarethafde771a2017-11-14 15:02:10 +010044import de.ids_mannheim.korap.web.filter.AuthenticationFilter;
margaretha894a7d72017-11-08 19:24:20 +010045import de.ids_mannheim.korap.web.filter.BlockingFilter;
46import de.ids_mannheim.korap.web.filter.DemoUserFilter;
47import de.ids_mannheim.korap.web.filter.PiwikFilter;
Michael Hanl482f30d2015-09-25 12:39:46 +020048import de.ids_mannheim.korap.web.utils.KustvaktResponseHandler;
Bodmo3d6bd352017-04-25 11:31:39 +020049
margaretha139d0f72017-11-14 18:56:22 +010050// import com.sun.xml.internal.messaging.saaj.util.Base64;
Michael Hanlfb839b92015-09-19 21:32:34 +020051
52/**
53 * @author hanl
54 * @date 24/01/2014
55 */
margaretha894a7d72017-11-08 19:24:20 +010056@Controller
margarethaecbe72d2017-07-26 17:13:32 +020057@Path("/auth")
Michael Hanlfb839b92015-09-19 21:32:34 +020058@ResourceFilters({ PiwikFilter.class })
59@Produces(MediaType.TEXT_HTML + ";charset=utf-8")
margarethad3c0fc92017-10-25 15:03:32 +020060public class AuthenticationController {
Michael Hanlfb839b92015-09-19 21:32:34 +020061
margaretha894a7d72017-11-08 19:24:20 +010062 @Autowired
margaretha4b5c1412017-11-15 20:55:04 +010063 private KustvaktResponseHandler kustvaktResponseHandler;
64
65 @Autowired
66 private HttpAuthorizationHandler authorizationHandler;
margaretha139d0f72017-11-14 18:56:22 +010067
margaretha4b5c1412017-11-15 20:55:04 +010068 @Autowired
69 private TransferEncoding transferEncoding;
70
margarethaf18298b2017-09-14 22:14:32 +020071 private static Boolean DEBUG_LOG = true;
72
Michael Hanlfb839b92015-09-19 21:32:34 +020073 //todo: bootstrap function to transmit certain default configuration settings and examples (example user queries,
74 // default usersettings, etc.)
margaretha139d0f72017-11-14 18:56:22 +010075 private static Logger jlog =
76 KustvaktLogger.getLogger(AuthenticationController.class);
Michael Hanlfb839b92015-09-19 21:32:34 +020077
margaretha894a7d72017-11-08 19:24:20 +010078 @Autowired
Michael Hanlfb839b92015-09-19 21:32:34 +020079 private AuthenticationManagerIface controller;
Michael Hanl8abaf9e2016-05-23 16:46:35 +020080
Michael Hanlfb839b92015-09-19 21:32:34 +020081 // private SendMail mail;
82
Michael Hanlfb839b92015-09-19 21:32:34 +020083 /**
Michael Hanl8abaf9e2016-05-23 16:46:35 +020084 * represents json string with data. All GUI clients can access
85 * this method to get certain default values
Michael Hanlfb839b92015-09-19 21:32:34 +020086 * --> security checks?
Michael Hanl8abaf9e2016-05-23 16:46:35 +020087 *
Michael Hanlfb839b92015-09-19 21:32:34 +020088 * @return String
89 */
Michael Hanl25aac542016-02-01 18:16:44 +010090 @Deprecated
Michael Hanlfb839b92015-09-19 21:32:34 +020091 @GET
92 @Path("bootstrap")
93 @Produces(MediaType.APPLICATION_JSON)
Michael Hanl8abaf9e2016-05-23 16:46:35 +020094 public Response bootstrap () {
Michael Hanlfb839b92015-09-19 21:32:34 +020095 Map m = new HashMap();
Michael Hanl8abaf9e2016-05-23 16:46:35 +020096 // m.put("settings", new UserSettings().toObjectMap());
Michael Hanldaf86602016-05-12 14:31:52 +020097 m.put("ql", BeansFactory.getKustvaktContext().getConfiguration()
Michael Hanlfb839b92015-09-19 21:32:34 +020098 .getQueryLanguages());
99 m.put("SortTypes", null); // types of sorting that are supported!
Michael Hanldaf86602016-05-12 14:31:52 +0200100 m.put("version", ServiceInfo.getInfo().getVersion());
margarethad4796662017-11-09 16:11:40 +0100101 try {
102 return Response.ok(JsonUtils.toJSON(m)).build();
103 }
104 catch (KustvaktException e) {
105 throw kustvaktResponseHandler.throwit(e);
106 }
Michael Hanlfb839b92015-09-19 21:32:34 +0200107 }
108
Michael Hanl8abaf9e2016-05-23 16:46:35 +0200109
Michael Hanl19390652016-01-16 11:01:24 +0100110 // fixme: moved to user
Michael Hanlfb839b92015-09-19 21:32:34 +0200111 @GET
112 @Path("status")
margarethafde771a2017-11-14 15:02:10 +0100113 @ResourceFilters({ AuthenticationFilter.class, DemoUserFilter.class,
margarethaf18298b2017-09-14 22:14:32 +0200114 BlockingFilter.class })
Michael Hanl8abaf9e2016-05-23 16:46:35 +0200115 public Response getStatus (@Context SecurityContext context,
Michael Hanlfb839b92015-09-19 21:32:34 +0200116 @HeaderParam(ContainerRequest.USER_AGENT) String agent,
117 @HeaderParam(ContainerRequest.HOST) String host,
118 @Context Locale locale) {
119 TokenContext ctx = (TokenContext) context.getUserPrincipal();
margarethad4796662017-11-09 16:11:40 +0100120 try {
121 return Response.ok(ctx.toJson()).build();
122 }
123 catch (KustvaktException e) {
124 throw kustvaktResponseHandler.throwit(e);
125 }
Michael Hanlfb839b92015-09-19 21:32:34 +0200126 }
127
Michael Hanl8abaf9e2016-05-23 16:46:35 +0200128
Michael Hanlfb839b92015-09-19 21:32:34 +0200129 @GET
130 @Path("apiToken")
Michael Hanl9be4e422016-07-21 14:13:27 +0200131 //@ResourceFilters({HeaderFilter.class})
margarethaf18298b2017-09-14 22:14:32 +0200132 public Response requestAPIToken (@Context HttpHeaders headers,
Michael Hanlfb839b92015-09-19 21:32:34 +0200133 @Context Locale locale,
134 @HeaderParam(ContainerRequest.USER_AGENT) String agent,
135 @HeaderParam(ContainerRequest.HOST) String host,
136 @HeaderParam("referer-url") String referer,
Bodmo3d6bd352017-04-25 11:31:39 +0200137 @QueryParam("scope") String scopes,
margarethaf18298b2017-09-14 22:14:32 +0200138 // @Context WebServiceContext wsContext, // FB
Bodmo3d6bd352017-04-25 11:31:39 +0200139 @Context SecurityContext secCtx) {
margarethaf18298b2017-09-14 22:14:32 +0200140
141 List<String> auth =
142 headers.getRequestHeader(ContainerRequest.AUTHORIZATION);
143 if (auth == null || auth.isEmpty()) {
margaretha894a7d72017-11-08 19:24:20 +0100144 throw kustvaktResponseHandler
margarethaf18298b2017-09-14 22:14:32 +0200145 .throwit(new KustvaktException(StatusCodes.MISSING_ARGUMENT,
146 "Authorization header is missing.",
147 "Authorization header"));
148 }
margaretha4b5c1412017-11-15 20:55:04 +0100149
150 String[] values;
151 try {
152 AuthorizationData authorizationData = authorizationHandler.
153 parseAuthorizationHeader(auth.get(0));
154 values = transferEncoding.decodeBase64(authorizationData.getToken());
155
156 }
157 catch (KustvaktException e) {
158 throw kustvaktResponseHandler.throwit(e);
159 }
Michael Hanlfb839b92015-09-19 21:32:34 +0200160
margarethaf18298b2017-09-14 22:14:32 +0200161 if (DEBUG_LOG == true) {
Bodmo3d6bd352017-04-25 11:31:39 +0200162 System.out.printf("Debug: AuthService.requestAPIToken...:\n");
margarethaf18298b2017-09-14 22:14:32 +0200163 System.out.printf("Debug: auth.size=%d\n", auth.size());
164 System.out.printf("auth.get(0)='%s'\n", auth.get(0));
margarethaf18298b2017-09-14 22:14:32 +0200165 /* hide password etc. - FB
166 if( auth.size() > 0 )
167 {
168 Iterator it = auth.iterator();
169 while( it.hasNext() )
170 System.out.printf(" header '%s'\n", it.next());
171 }
172 if( values.length > 0 )
173 {
174 for(int i=0; i< values.length; i++)
175 {
176 System.out.printf(" values[%d]='%s'\n", i, values[i]);
177 }
178 }
179 */
180 MultivaluedMap<String, String> headerMap =
181 headers.getRequestHeaders();
182 if (headerMap != null && headerMap.size() > 0) {
183 Iterator<String> it = headerMap.keySet().iterator();
184 while (it.hasNext()) {
185 String key = (String) it.next();
186 List<String> vals = headerMap.get(key);
margaretha4b5c1412017-11-15 20:55:04 +0100187// System.out.printf("Debug: requestAPIToken: '%s' = '%s'\n",
188// key, vals);
margarethaf18298b2017-09-14 22:14:32 +0200189 }
190
191 }
margaretha4b5c1412017-11-15 20:55:04 +0100192// System.out.printf("Debug: requestAPIToken: isSecure = %s.\n",
193// secCtx.isSecure() ? "yes" : "no");
margarethaf18298b2017-09-14 22:14:32 +0200194 } // DEBUG_LOG
195
Michael Hanlfb839b92015-09-19 21:32:34 +0200196 // "Invalid syntax for username and password"
197 if (values == null)
margaretha894a7d72017-11-08 19:24:20 +0100198 throw kustvaktResponseHandler.throwit(StatusCodes.ACCESS_DENIED);
Michael Hanlfb839b92015-09-19 21:32:34 +0200199
Michael Hanl8abaf9e2016-05-23 16:46:35 +0200200 if (values[0].equalsIgnoreCase("null")
201 | values[1].equalsIgnoreCase("null"))
Michael Hanlfb839b92015-09-19 21:32:34 +0200202 // is actual an invalid request
margaretha894a7d72017-11-08 19:24:20 +0100203 throw kustvaktResponseHandler.throwit(StatusCodes.REQUEST_INVALID);
Michael Hanlfb839b92015-09-19 21:32:34 +0200204
Michael Hanl5fac8ab2016-01-29 16:33:04 +0100205 Map<String, Object> attr = new HashMap<>();
Michael Hanl482f30d2015-09-25 12:39:46 +0200206 if (scopes != null && !scopes.isEmpty())
207 attr.put(Attributes.SCOPES, scopes);
Michael Hanlfb839b92015-09-19 21:32:34 +0200208 attr.put(Attributes.HOST, host);
209 attr.put(Attributes.USER_AGENT, agent);
margarethaf18298b2017-09-14 22:14:32 +0200210
Michael Hanlfb839b92015-09-19 21:32:34 +0200211 TokenContext context;
212 try {
Bodmo3d6bd352017-04-25 11:31:39 +0200213 // User user = controller.authenticate(0, values[0], values[1], attr); Implementation by Hanl
margaretha139d0f72017-11-14 18:56:22 +0100214 User user = controller.authenticate(AuthenticationType.LDAP,
215 values[0], values[1], attr); // Implementation with IdM/LDAP
Bodmo3d6bd352017-04-25 11:31:39 +0200216 // Userdata data = this.controller.getUserData(user, UserDetails.class); // Implem. by Hanl
Michael Hanl5fac8ab2016-01-29 16:33:04 +0100217 // todo: is this necessary?
Michael Hanl8abaf9e2016-05-23 16:46:35 +0200218 // attr.putAll(data.fields());
Bodmoca3dcfb2017-05-24 16:36:00 +0200219 controller.setAccessAndLocation(user, headers);
margarethaf18298b2017-09-14 22:14:32 +0200220 if (DEBUG_LOG == true) System.out.printf(
221 "Debug: /apiToken/: location=%s, access='%s'.\n",
222 user.locationtoString(), user.accesstoString());
Bodmoca3dcfb2017-05-24 16:36:00 +0200223 attr.put(Attributes.LOCATION, user.getLocation());
margarethaf18298b2017-09-14 22:14:32 +0200224 attr.put(Attributes.CORPUS_ACCESS, user.getCorpusAccess());
225 context = controller.createTokenContext(user, attr,
margaretha4de41192017-11-15 11:47:11 +0100226 AuthenticationType.LDAP);
margaretha139d0f72017-11-14 18:56:22 +0100227 //Attributes.API_AUTHENTICATION);
Michael Hanl8abaf9e2016-05-23 16:46:35 +0200228 }
229 catch (KustvaktException e) {
margaretha894a7d72017-11-08 19:24:20 +0100230 throw kustvaktResponseHandler.throwit(e);
Michael Hanlfb839b92015-09-19 21:32:34 +0200231 }
232
margarethad4796662017-11-09 16:11:40 +0100233 try {
234 return Response.ok(context.toJson()).build();
235 }
236 catch (KustvaktException e) {
237 throw kustvaktResponseHandler.throwit(e);
238 }
Michael Hanlfb839b92015-09-19 21:32:34 +0200239 }
240
Michael Hanl8abaf9e2016-05-23 16:46:35 +0200241
Michael Hanlfb839b92015-09-19 21:32:34 +0200242 // todo:
243 @Deprecated
244 @GET
245 @Path("refresh")
Michael Hanl8abaf9e2016-05-23 16:46:35 +0200246 public Response refresh (@Context SecurityContext context,
Michael Hanlfb839b92015-09-19 21:32:34 +0200247 @Context Locale locale) {
248 TokenContext ctx = (TokenContext) context.getUserPrincipal();
249 TokenContext newContext;
250
251 // try {
252 // newContext = controller.refresh(ctx);
253 // }catch (KorAPException e) {
254 // KorAPLogger.ERROR_LOGGER.error("Exception encountered!", e);
Michael Hanl482f30d2015-09-25 12:39:46 +0200255 // throw KustvaktResponseHandler.throwit(e);
Michael Hanlfb839b92015-09-19 21:32:34 +0200256 // }
257 // return Response.ok().entity(newContext.getToken()).build();
258 return null;
259 }
260
Michael Hanl8abaf9e2016-05-23 16:46:35 +0200261
Michael Hanlfb839b92015-09-19 21:32:34 +0200262 @GET
263 @Path("sessionToken")
Michael Hanl9be4e422016-07-21 14:13:27 +0200264 //@ResourceFilters({HeaderFilter.class})
Michael Hanl8abaf9e2016-05-23 16:46:35 +0200265 public Response requestSession (@Context HttpHeaders headers,
Michael Hanlfb839b92015-09-19 21:32:34 +0200266 @Context Locale locale,
267 @HeaderParam(ContainerRequest.USER_AGENT) String agent,
268 @HeaderParam(ContainerRequest.HOST) String host) {
margarethaf18298b2017-09-14 22:14:32 +0200269 List<String> auth =
270 headers.getRequestHeader(ContainerRequest.AUTHORIZATION);
Michael Hanlfb839b92015-09-19 21:32:34 +0200271
margaretha4b5c1412017-11-15 20:55:04 +0100272 String[] values;
273 try {
274 AuthorizationData authorizationData = authorizationHandler.
275 parseAuthorizationHeader(auth.get(0));
276 values = transferEncoding.decodeBase64(authorizationData.getToken());
277
278 }
279 catch (KustvaktException e) {
280 throw kustvaktResponseHandler.throwit(e);
281 }
Michael Hanlfb839b92015-09-19 21:32:34 +0200282 // authentication = StringUtils.stripTokenType(authentication);
283 // String[] values = new String(
284 // DatatypeConverter.parseBase64Binary(authentication)).split(":");
285 // String[] values = Base64.base64Decode(authentication).split(":");
286
287 // "Invalid syntax for username and password"
Michael Hanlfb839b92015-09-19 21:32:34 +0200288
Bodmo3d6bd352017-04-25 11:31:39 +0200289 // Implementation Hanl mit '|'. 16.02.17/FB
290 //if (values[0].equalsIgnoreCase("null")
291 // | values[1].equalsIgnoreCase("null"))
Michael Hanl8abaf9e2016-05-23 16:46:35 +0200292 if (values[0].equalsIgnoreCase("null")
Bodmo3d6bd352017-04-25 11:31:39 +0200293 || values[1].equalsIgnoreCase("null"))
margaretha894a7d72017-11-08 19:24:20 +0100294 throw kustvaktResponseHandler.throwit(StatusCodes.REQUEST_INVALID);
Michael Hanlfb839b92015-09-19 21:32:34 +0200295
Michael Hanl5fac8ab2016-01-29 16:33:04 +0100296 Map<String, Object> attr = new HashMap<>();
Michael Hanlfb839b92015-09-19 21:32:34 +0200297 attr.put(Attributes.HOST, host);
298 attr.put(Attributes.USER_AGENT, agent);
299 TokenContext context;
margarethaf18298b2017-09-14 22:14:32 +0200300 String contextJson;
Michael Hanlfb839b92015-09-19 21:32:34 +0200301 try {
margaretha139d0f72017-11-14 18:56:22 +0100302 User user = controller.authenticate(AuthenticationType.SESSION,
303 values[0], values[1], attr);
Michael Hanlfb839b92015-09-19 21:32:34 +0200304 context = controller.createTokenContext(user, attr,
margaretha4de41192017-11-15 11:47:11 +0100305 AuthenticationType.SESSION);
margarethaf18298b2017-09-14 22:14:32 +0200306 contextJson = context.toJson();
307 jlog.debug(contextJson);
Michael Hanl8abaf9e2016-05-23 16:46:35 +0200308 }
309 catch (KustvaktException e) {
margaretha894a7d72017-11-08 19:24:20 +0100310 throw kustvaktResponseHandler.throwit(e);
Michael Hanlfb839b92015-09-19 21:32:34 +0200311 }
margarethaf18298b2017-09-14 22:14:32 +0200312 return Response.ok().entity(contextJson).build();
Michael Hanlfb839b92015-09-19 21:32:34 +0200313 }
314
Michael Hanl8abaf9e2016-05-23 16:46:35 +0200315
Michael Hanlfb839b92015-09-19 21:32:34 +0200316 // fixme: security issues: setup shibboleth compatible authentication system
317 // todo: will be purged with token authentication --> shib is client side
318 @POST
Michael Hanl6bfe4002016-07-02 11:43:09 +0200319 @Consumes(MediaType.APPLICATION_JSON)
Michael Hanlfb839b92015-09-19 21:32:34 +0200320 @Produces("application/json")
321 @Path("shibboleth")
Michael Hanl8abaf9e2016-05-23 16:46:35 +0200322 public Response loginshib (@Context HttpHeaders headers,
Michael Hanlfb839b92015-09-19 21:32:34 +0200323 @Context Locale locale,
324 @HeaderParam(ContainerRequest.USER_AGENT) String agent,
325 @HeaderParam(ContainerRequest.HOST) String host,
326 @QueryParam("client_id") String client_id) {
327
328 // the shibfilter decrypted the values
329 // define default provider for returned access token strategy?!
330
Michael Hanl5fac8ab2016-01-29 16:33:04 +0100331 Map<String, Object> attr = new HashMap<>();
Michael Hanlfb839b92015-09-19 21:32:34 +0200332 attr.put(Attributes.HOST, host);
333 attr.put(Attributes.USER_AGENT, agent);
334
335 TokenContext context;
336
337 try {
338 // todo: distinguish type KorAP/Shibusers
margaretha139d0f72017-11-14 18:56:22 +0100339 User user = controller.authenticate(AuthenticationType.SHIBBOLETH,
340 null, null, attr);
Michael Hanlfb839b92015-09-19 21:32:34 +0200341 context = controller.createTokenContext(user, attr, null);
Michael Hanl8abaf9e2016-05-23 16:46:35 +0200342 }
343 catch (KustvaktException e) {
margaretha894a7d72017-11-08 19:24:20 +0100344 throw kustvaktResponseHandler.throwit(e);
Michael Hanlfb839b92015-09-19 21:32:34 +0200345 }
margarethad4796662017-11-09 16:11:40 +0100346 try {
347 return Response.ok().entity(context.toJson()).build();
348 }
349 catch (KustvaktException e) {
350 throw kustvaktResponseHandler.throwit(e);
351 }
Michael Hanlfb839b92015-09-19 21:32:34 +0200352 }
Michael Hanl19390652016-01-16 11:01:24 +0100353
Michael Hanl8abaf9e2016-05-23 16:46:35 +0200354
Michael Hanl19390652016-01-16 11:01:24 +0100355 //fixme: moved from userservice
356 @GET
357 @Path("logout")
margarethafde771a2017-11-14 15:02:10 +0100358 @ResourceFilters({ AuthenticationFilter.class, DemoUserFilter.class,
margarethaf18298b2017-09-14 22:14:32 +0200359 PiwikFilter.class })
360 public Response logout (@Context SecurityContext ctx,
361 @Context Locale locale) {
Michael Hanl19390652016-01-16 11:01:24 +0100362 TokenContext context = (TokenContext) ctx.getUserPrincipal();
363 try {
364 controller.logout(context);
Michael Hanl8abaf9e2016-05-23 16:46:35 +0200365 }
366 catch (KustvaktException e) {
Michael Hanl00ef5462016-06-06 17:39:59 +0200367 jlog.error("Logout Exception: {}", e.string());
margaretha894a7d72017-11-08 19:24:20 +0100368 throw kustvaktResponseHandler.throwit(e);
Michael Hanl19390652016-01-16 11:01:24 +0100369 }
370 return Response.ok().build();
371 }
372
Michael Hanlfb839b92015-09-19 21:32:34 +0200373}