/******************************************************************************* * Copyright 2014 Miami-Dade County * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package org.sharegov.cirm.rest; import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.net.URLDecoder; import java.net.UnknownHostException; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.ws.rs.core.Context; import javax.ws.rs.core.Cookie; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Request; import javax.ws.rs.core.SecurityContext; import javax.ws.rs.core.UriInfo; import org.restlet.data.ClientInfo; import org.semanticweb.owlapi.model.OWLNamedIndividual; import org.sharegov.cirm.OWL; import org.sharegov.cirm.Refs; import org.sharegov.cirm.RequestScopeFilter; import org.sharegov.cirm.StartUp; import org.sharegov.cirm.utils.GenUtils; import org.sharegov.cirm.utils.Ref; import org.sharegov.cirm.utils.RequestScopeRef; import org.sharegov.cirm.utils.ThreadLocalStopwatch; /** * <p> * Top level class for CiRM REST services. * </p> * * <p> * This class offers facilities for dealing with currently logged in user and their permissions. * </p> * * @author Borislav Iordanov, Thomas Hilpold * */ public class RestService { public @Context SecurityContext security = null; public @Context HttpHeaders httpHeaders = null; public @Context UriInfo uriInfo = null; public @Context Request request = null; public static boolean DBG = true; public static volatile RequestScopeRef<Boolean> forceClientExempt = new RequestScopeRef<Boolean>(new Ref<Boolean>() { public Boolean resolve() { return false; } }); /** * Value: one or array of exempt client Hostname individuals */ public static final String EXEMPT_CONFIG_PARAM_KEY = "CirmExemptClientConfig"; private static volatile Map<String, String> exemptClientIpToHost; // for some reason this is not working, service becomes inaccessible with // Restlet framework // if this is enabled... // public @Context Response response = null; public String getUserId() { if(httpHeaders == null) { ThreadLocalStopwatch.getWatch().time("ERROR: RestService.getUserId() httpHeaders were null. this indicates that this object: " + this + " was called without context. " + "\r\n Remove this after all such problems are found and fixed:"); GenUtils.logStackTrace(Thread.currentThread().getStackTrace(), 10); return ""; } Cookie cookie = httpHeaders.getCookies().get("username"); if (cookie != null && cookie.getValue() != null && cookie.getValue().length() > 0) return cookie.getValue(); else return "anonymous"; } public String [] getUserGroups() { if(httpHeaders == null) { if (DBG) { ThreadLocalStopwatch.getWatch().time("ERROR: RestService.getUserGroups() httpHeaders were null. this indicates that this object: " + this + " was called without context. " + "\r\n Remove this after all such problems are found and fixed:"); GenUtils.logStackTrace(Thread.currentThread().getStackTrace(), 10); } return new String[0]; } Cookie cookie = httpHeaders.getCookies().get("usergroups"); if (cookie != null && cookie.getValue() != null && cookie.getValue().length() > 0) { // ,$Version=1 observed in tests, must be excluded. String cookieVal = cookie.getValue(); int versionIndex = cookieVal.indexOf(",$"); if (versionIndex > 0) cookieVal = cookieVal.substring(0, versionIndex); try { return URLDecoder.decode(cookieVal, "UTF-8").split(";"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } else return new String[0]; } public Set<OWLNamedIndividual> getUserActors() { HashSet<OWLNamedIndividual> S = new HashSet<OWLNamedIndividual>(); for (String groupName : getUserGroups()) S.add(OWL.individual(groupName)); return S; } /** * Get User information consisting of UserId and -Groups. * @return */ public String getUserInfo() { StringBuffer result = new StringBuffer(200); result.append("UId: " + getUserId() + " GIds: "); for (String g : getUserGroups()) { result.append(g); result.append(" "); } return result.toString(); } public static Map<String,String> getClientExemptIpToHostMap() { if (exemptClientIpToHost == null) synchronized (RestService.class) { if (exemptClientIpToHost == null) initClientExemptCache(); } return exemptClientIpToHost; } private static synchronized void initClientExemptCache() { Map<String, String> exemptClientIpToHost = new HashMap<String, String>(); Set<String> hosts = getClientExemptHostsFromConfiguration(); for (String hostname : hosts) { try { InetAddress[] exemptIps = InetAddress.getAllByName(hostname); for (InetAddress exemptIp : exemptIps) { exemptClientIpToHost.put(exemptIp.getHostAddress(), hostname); ThreadLocalStopwatch.getWatch().time("RestService: ClientExemptCache intialized with host " + hostname + "(" + exemptIp.getHostAddress() + ")."); } } catch (UnknownHostException e) { System.err.println("RestService: initClientExemptCache: Unknownhost Exception: Could not resolve hostname to IPs for " + hostname); } } //publish to volatile for all threads RestService.exemptClientIpToHost = Collections.unmodifiableMap(exemptClientIpToHost); } @SuppressWarnings("rawtypes") private static Set<String> getClientExemptHostsFromConfiguration() { Set<String> result = new HashSet<String>(); Object values = Refs.configSet.resolve().get(EXEMPT_CONFIG_PARAM_KEY); if (values instanceof Set) { for (Object value : ((Set)values)) { result.add(((OWLNamedIndividual)value).getIRI().getFragment()); } } else { result.add(((OWLNamedIndividual)values).getIRI().getFragment()); } return result; } public static synchronized void clearClientExemptCache() { exemptClientIpToHost = null; } public boolean isClientExempt() { if (StartUp.getConfig().is("allClientsExempt", true) || Boolean.TRUE.equals(Refs.configSet.resolve().get("areAllClientsExempt"))) { return true; } else if (forceClientExempt.resolve()) { return true; } else if (isClientCirmAdmin()) { return true; } else { String exemptHostName = null; ClientInfo clientInfo = (ClientInfo)RequestScopeFilter.get("clientInfo"); if (clientInfo != null) { String clientIp = clientInfo.getAddress(); exemptHostName = getClientExemptIpToHostMap().get(clientIp); if (DBG && exemptHostName != null) { ThreadLocalStopwatch.getWatch().time("RestService: Granting exempt client access to " + exemptHostName + " (" + clientIp + ")"); } } return exemptHostName != null; } } public boolean isClientCirmAdmin() { return Arrays.asList(getUserGroups()).contains(UserService.CIRM_ADMIN); } }