001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.core.api.config.property.ConfigContext;
020import org.kuali.rice.core.api.exception.RiceRuntimeException;
021import org.kuali.rice.kim.api.identity.Person;
022import org.kuali.rice.kim.api.services.KimApiServiceLocator;
023import org.kuali.rice.krad.util.SessionTicket;
024
025import java.io.Serializable;
026import java.util.ArrayList;
027import java.util.Collections;
028import java.util.HashMap;
029import java.util.List;
030import java.util.Map;
031import java.util.concurrent.atomic.AtomicInteger;
032
033/**
034 * Holds info about the User Session
035 *
036 * @author Kuali Rice Team (rice.collab@kuali.org)
037 */
038public class UserSession implements Serializable {
039    private static final long serialVersionUID = 4532616762540067557L;
040
041    private Person person;
042    private Person backdoorUser;
043    private AtomicInteger nextObjectKey;
044    private Map<String, Object> objectMap;
045    private String kualiSessionId;
046
047    /**
048     * Returns the session id. The session id is a unique identifier for the session.
049     * @return the kualiSessionId
050     */
051    public String getKualiSessionId() {
052        return this.kualiSessionId;
053    }
054
055    /**
056     * Sets the session id.
057     * @param kualiSessionId the kualiSessionId to set
058     */
059    public void setKualiSessionId(String kualiSessionId) {
060        this.kualiSessionId = kualiSessionId;
061    }
062
063    /**
064     * Creates a user session for the principal specified in the parameter.
065     * Take in a netid, and construct the user from that.
066     *
067     * @param principalName
068     */
069    public UserSession(String principalName) {
070        initPerson(principalName);
071        this.nextObjectKey = new AtomicInteger(0);
072        this.objectMap = Collections.synchronizedMap(new HashMap<String,Object>());
073    }
074
075    /**
076     * Loads the Person object from KIM. Factored out for testability.
077     * @param principalName the principalName
078     */
079    protected void initPerson(String principalName) {
080        this.person = KimApiServiceLocator.getPersonService().getPersonByPrincipalName(principalName);
081        if (this.person == null) {
082            throw new IllegalArgumentException(
083                    "Failed to locate a principal with principal name '" + principalName + "'");
084        }
085    }
086
087    /**
088     * Returns the id of the current user.
089     * @return the principalId of the current user in the system, backdoor principalId if backdoor is set
090     */
091    public String getPrincipalId() {
092        if (backdoorUser != null) {
093            return backdoorUser.getPrincipalId();
094        }
095        return person.getPrincipalId();
096    }
097
098    /**
099     * Returns the name of the current user.
100     * @return the principalName of the current user in the system, backdoor principalName if backdoor is set
101     */
102    public String getPrincipalName() {
103        if (backdoorUser != null) {
104            return backdoorUser.getPrincipalName();
105        }
106        return person.getPrincipalName();
107    }
108
109    /**
110     * Returns who is logged in. If the backdoor is in use, this will return the network id of the person that is
111     * standing in as the backdoor user.
112     *
113     * @return String
114     */
115    public String getLoggedInUserPrincipalName() {
116        if (person != null) {
117            return person.getPrincipalName();
118        }
119        return "";
120    }
121
122    /**
123     * Returns a Person object for the current user.
124     * @return the KualiUser which is the current user in the system, backdoor if backdoor is set
125     */
126    public Person getPerson() {
127        if (backdoorUser != null) {
128            return backdoorUser;
129        }
130        return person;
131    }
132
133    /**
134     * Returns the actual current user even if the backdoor is in use.
135     * @return the KualiUser which is the current user in the system
136     */
137    public Person getActualPerson() {
138        return person;
139    }
140
141    /**
142     * override the current user in the system by setting the backdoor networkId, which is useful when dealing with
143     * routing or other reasons why you would need to assume an identity in the system
144     *
145     * @param principalName
146     */
147    public void setBackdoorUser(String principalName) {
148        // only allow backdoor in non-production environments
149        if (!isProductionEnvironment()) {
150            this.backdoorUser = KimApiServiceLocator.getPersonService().getPersonByPrincipalName(principalName);
151        }
152    }
153
154    private boolean isProductionEnvironment() {
155        return ConfigContext.getCurrentContextConfig().isProductionEnvironment();
156    }
157
158    /**
159     * clear the backdoor user
160     */
161    public void clearBackdoorUser() {
162        this.backdoorUser = null;
163    }
164
165    /**
166     * allows adding an arbitrary object to the session and returns a string key that can be used to later access this
167     * object from
168     * the session using the retrieveObject method in this class. This allows for a prefix to be placed in front of the
169     * incremented key. So if the prefix is "searchResults" and the nextObjectKey (local int that holds the key value)
170     * is 2 then
171     * the new key will be "searchResults3". "searchResults3" will be returned from the method.
172     *
173     * @param object
174     */
175    public String addObjectWithGeneratedKey(Serializable object, String keyPrefix) {
176        String objectKey = keyPrefix + nextObjectKey.incrementAndGet();
177        objectMap.put(objectKey, object);
178        return objectKey;
179    }
180
181    /**
182     * allows adding an arbitrary object to the session and returns a string key that can be used to later access this
183     * object from
184     * the session using the retrieveObject method in this class. The key is generated from an integer and incremented
185     * for every
186     * object added.  So the first object added with have a key of "1".  This key will be returned from the method.
187     *
188     * @param object
189     */
190    public String addObjectWithGeneratedKey(Object object) {
191        String objectKey = nextObjectKey.incrementAndGet() + "";
192        objectMap.put(objectKey, object);
193        return objectKey;
194    }
195
196    /**
197     * allows adding an arbitrary object to the session with static a string key that can be used to later access this
198     * object from
199     * the session using the retrieveObject method in this class
200     *
201     * @param object
202     */
203    public void addObject(String key, Object object) {
204        objectMap.put(key, object);
205    }
206
207    /**
208     * allows for fetching an object that has been put into the userSession based on the key that would have been
209     * returned when
210     * adding the object
211     *
212     * @param objectKey
213     */
214    public Object retrieveObject(String objectKey) {
215        return this.objectMap.get(objectKey);
216    }
217
218    /**
219     * allows for removal of an object from session that has been put into the userSession based on the key that would
220     * have been
221     * assigned
222     *
223     * @param objectKey
224     */
225    public void removeObject(String objectKey) {
226        this.objectMap.remove(objectKey);
227    }
228
229    /**
230     * allows for removal of an object from session that has been put into the userSession based on a key that starts
231     * with the given
232     * prefix
233     */
234    public void removeObjectsByPrefix(String objectKeyPrefix) {
235        synchronized (objectMap) {
236            List<String> removeKeys = new ArrayList<String>();
237            for (String key : objectMap.keySet()) {
238                if (key.startsWith(objectKeyPrefix)) {
239                    removeKeys.add(key);
240                }
241            }
242
243            for (String key : removeKeys) {
244                this.objectMap.remove(key);
245            }
246        }
247    }
248
249    /**
250     * @return boolean indicating if the backdoor is in use
251     */
252    public boolean isBackdoorInUse() {
253        return backdoorUser != null;
254    }
255
256    /**
257     * Adds the given SessionTicket to the objectMap and returns the associated key
258     *
259     * @param ticket - SessionTicket to add
260     * @return the objectMap key for the ticket as a String
261     */
262    public String putSessionTicket(SessionTicket ticket) {
263        return addObjectWithGeneratedKey(ticket);
264    }
265
266    /**
267     * Retrieves all SessionTicket instances currently in the UserSession#objectMap
268     *
269     * @return List<SessionTicket> contained in user session
270     */
271    public List<SessionTicket> getAllSessionTickets() {
272        List<SessionTicket> sessionTickets = new ArrayList<SessionTicket>();
273
274        synchronized (objectMap) {
275            for (Object object : objectMap.values()) {
276                if (object instanceof SessionTicket) {
277                    sessionTickets.add((SessionTicket) object);
278                }
279            }
280        }
281
282        return sessionTickets;
283    }
284
285    /**
286     * Retrieves all SessionTicket instances currently in the UserSession#objectMap that are of a given ticket type
287     *
288     * @return List<SessionTicket> contained in user session
289     */
290    public List<SessionTicket> getAllSessionTicketsByType(String ticketTypeName) {
291        List<SessionTicket> sessionTickets = new ArrayList<SessionTicket>();
292
293        for (SessionTicket ticket : getAllSessionTickets()) {
294            if (StringUtils.equalsIgnoreCase(ticket.getTicketTypeName(), ticketTypeName)) {
295                sessionTickets.add(ticket);
296            }
297        }
298
299        return sessionTickets;
300    }
301
302    /**
303     * Determines if the UserSession contains a ticket of the given type that matches the given context. To match context
304     * the ticket must
305     * contain all the same keys at the given context and the values must be equal with the exception of case
306     *
307     * @param ticketTypeName - Name of the ticket type to match
308     * @param matchContext - Map on context parameters to match on
309     * @return true if a ticket was found in the UserSession that matches the request, false if one was not found
310     */
311    public boolean hasMatchingSessionTicket(String ticketTypeName, Map<String, String> matchContext) {
312        boolean hasTicket = false;
313
314        for (SessionTicket ticket : getAllSessionTicketsByType(ticketTypeName)) {
315            Map<String, String> ticketContext = ticket.getTicketContext();
316
317            boolean keySetMatch = ticketContext.keySet().equals(matchContext.keySet());
318            if (keySetMatch) {
319                boolean valuesMatch = true;
320                for (String contextKey : ticketContext.keySet()) {
321                    String ticketValue = ticketContext.get(contextKey);
322                    String matchValue = matchContext.get(contextKey);
323                    if (!StringUtils.equalsIgnoreCase(ticketValue, matchValue)) {
324                        valuesMatch = false;
325                    }
326                }
327
328                if (valuesMatch) {
329                    hasTicket = true;
330                    break;
331                }
332            }
333        }
334
335        return hasTicket;
336    }
337
338    /**
339     * retrieves an unmodifiable view of the objectMap.
340     */
341    public Map<String, Object> getObjectMap() {
342        return Collections.unmodifiableMap(this.objectMap);
343    }
344
345    /**
346     * clear the objectMap
347     */
348    public void clearObjectMap() {
349        this.objectMap = Collections.synchronizedMap(new HashMap<String,Object>());
350    }
351}