001: /***************************************************************
002: * This file is part of the [fleXive](R) project.
003: *
004: * Copyright (c) 1999-2008
005: * UCS - unique computing solutions gmbh (http://www.ucs.at)
006: * All rights reserved
007: *
008: * The [fleXive](R) project is free software; you can redistribute
009: * it and/or modify it under the terms of the GNU General Public
010: * License as published by the Free Software Foundation;
011: * either version 2 of the License, or (at your option) any
012: * later version.
013: *
014: * The GNU General Public License can be found at
015: * http://www.gnu.org/copyleft/gpl.html.
016: * A copy is found in the textfile GPL.txt and important notices to the
017: * license from the author are found in LICENSE.txt distributed with
018: * these libraries.
019: *
020: * This library is distributed in the hope that it will be useful,
021: * but WITHOUT ANY WARRANTY; without even the implied warranty of
022: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
023: * GNU General Public License for more details.
024: *
025: * For further information about UCS - unique computing solutions gmbh,
026: * please see the company website: http://www.ucs.at
027: *
028: * For further information about [fleXive](R), please see the
029: * project website: http://www.flexive.org
030: *
031: *
032: * This copyright notice MUST APPEAR in all copies of the file!
033: ***************************************************************/package com.flexive.core.security;
034:
035: import com.flexive.shared.CacheAdmin;
036: import com.flexive.shared.EJBLookup;
037: import com.flexive.shared.FxContext;
038: import com.flexive.shared.cache.FxCacheException;
039: import com.flexive.shared.exceptions.FxApplicationException;
040: import com.flexive.shared.exceptions.FxLoadException;
041: import com.flexive.shared.exceptions.FxNoAccessException;
042: import com.flexive.shared.exceptions.FxNotFoundException;
043: import com.flexive.shared.interfaces.AccountEngine;
044: import com.flexive.shared.mbeans.FxCacheMBean;
045: import com.flexive.shared.security.*;
046: import org.apache.commons.logging.Log;
047: import org.apache.commons.logging.LogFactory;
048:
049: import javax.security.auth.Subject;
050: import java.util.ArrayList;
051: import java.util.List;
052: import java.util.Set;
053:
054: /**
055: * Store for all currently logged in user(ticket)s
056: *
057: * @author Gregor Schober (gregor.schober@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
058: * @author Markus Plesser (markus.plesser@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
059: */
060: public class UserTicketStore {
061:
062: private static final String KEY_TICKET = "Ticket";
063: private static transient Log LOG = LogFactory
064: .getLog(UserTicketStore.class);
065:
066: /**
067: * Helper class
068: */
069: static class SubjectWithPath {
070: Subject subject;
071: String path;
072: }
073:
074: /**
075: * Stores a subject (and its ticket) for the current session.
076: *
077: * @param sub the subject to store
078: */
079: public static void storeSubject(Subject sub) {
080: FxCacheMBean cache = CacheAdmin.getInstance();
081: FxContext si = FxContext.get();
082: try {
083: cache.put(getCacheRoot(si), KEY_TICKET, sub);
084: if (LOG.isDebugEnabled())
085: LOG.debug("Storing at [" + getCacheRoot(si) + "]: ["
086: + sub + "]");
087: } catch (FxCacheException exc) {
088: LOG.error("Failed to store ticket in UserTickerStore: "
089: + exc.getMessage(), exc);
090: System.err
091: .println("Failed to store ticket in UserTickerStore: "
092: + exc.getMessage());
093: }
094: }
095:
096: /**
097: * Stores a subject (and its ticket) for the given path
098: *
099: * @param sub the subject to store
100: */
101: private static void storeSubject(SubjectWithPath sub) {
102: FxCacheMBean cache = CacheAdmin.getInstance();
103: try {
104: cache.put(CacheAdmin.ROOT_USERTICKETSTORE + "/" + sub.path,
105: KEY_TICKET, sub.subject);
106: if (LOG.isDebugEnabled())
107: LOG.debug("Storing at ["
108: + CacheAdmin.ROOT_USERTICKETSTORE + "/"
109: + sub.path + "]: [" + sub.subject + "]");
110: } catch (FxCacheException exc) {
111: LOG.error("Failed to store ticket in UserTickerStore: "
112: + exc.getMessage(), exc);
113: }
114: }
115:
116: /**
117: * Removes the subject for the current session
118: */
119: public static void removeSubject() {
120: FxCacheMBean cache = CacheAdmin.getInstance();
121: FxContext si = FxContext.get();
122: try {
123: cache.remove(getCacheRoot(si), KEY_TICKET);
124: if (LOG.isDebugEnabled())
125: LOG.debug("Removing all subjects at for ["
126: + getCacheRoot(si) + "]");
127: } catch (FxCacheException exc) {
128: LOG.error("Failed to clear session in UserTickerStore: "
129: + exc.getMessage(), exc);
130: }
131: }
132:
133: /**
134: * Computes the ticket store path of a session.
135: *
136: * @param si the session info
137: * @return the path of the ticket for the session
138: */
139: protected static String getCacheRoot(final FxContext si) {
140: return CacheAdmin.ROOT_USERTICKETSTORE + "/"
141: + si.getApplicationId()
142: + (si.isWebDAV() ? "_WebDav" : "") + ":"
143: + si.getSessionId();
144: }
145:
146: /**
147: * Gets the ticket of the current request.
148: *
149: * @param si the request information
150: * @return the ticket of the current request.
151: */
152: protected static Subject getSubject(FxContext si) {
153: FxCacheMBean cache = CacheAdmin.getInstance();
154: Subject sub = null;
155: try {
156: sub = (Subject) cache.get(getCacheRoot(si), KEY_TICKET);
157: if (LOG.isDebugEnabled())
158: LOG.debug("getSubject returned for ["
159: + getCacheRoot(si) + "]: " + sub);
160: } catch (FxCacheException exc) {
161: LOG.error(
162: "Failed to get ticket from UserTicketStore Cache: "
163: + exc.getMessage(), exc);
164: } catch (Exception exc) {
165: LOG.error("Failed to get ticket from UserTicketStore: "
166: + exc.getMessage(), exc);
167: }
168: return sub;
169: }
170:
171: /**
172: * Returns all subjects(tickets) in the store that match at least one of the given parameters.
173: *
174: * @param userId the user id to look for
175: * @param groupId the group ids to match, if the ticket is a member of at least
176: * one group it is added to the result
177: * @param acls the acls
178: * @return the matching subjects
179: */
180: private static SubjectWithPath[] getSubjects(Long userId,
181: long[] groupId, long[] acls) {
182: FxCacheMBean cache = CacheAdmin.getInstance();
183: try {
184: List<SubjectWithPath> result = new ArrayList<SubjectWithPath>(
185: 50);
186: Set aSet = cache
187: .getChildrenNames(CacheAdmin.ROOT_USERTICKETSTORE);
188: if (aSet == null)
189: return new SubjectWithPath[0];
190: UserTicketImpl aTicket;
191: Subject sub;
192: for (Object subPath : aSet) {
193: sub = (Subject) cache
194: .get(CacheAdmin.ROOT_USERTICKETSTORE + "/"
195: + subPath, KEY_TICKET);
196: if (sub == null)
197: continue;
198: try {
199: aTicket = (UserTicketImpl) FxDefaultLogin
200: .getUserTicket(sub);
201: } catch (FxNotFoundException exc) {
202: LOG.error("No UserTicket found in Subject " + sub,
203: exc);
204: continue;
205: }
206: boolean match = userId != null
207: && aTicket.getUserId() == userId;
208: match = match || aTicket.isInAtLeastOneGroup(groupId);
209: match = match || aTicket.hasAtLeastOneACL(acls);
210: if (!match)
211: continue;
212: SubjectWithPath sp = new SubjectWithPath();
213: sp.subject = sub;
214: sp.path = String.valueOf(subPath);
215: result.add(sp);
216: }
217: return result.toArray(new SubjectWithPath[result.size()]);
218: } catch (FxCacheException exc) {
219: LOG.error("Failed to examine tickets: " + exc.getMessage(),
220: exc);
221: return new SubjectWithPath[0];
222: }
223: }
224:
225: /**
226: * Gets the user ticket for the current request.
227: *
228: * @return the user ticket for the current request.
229: */
230: public static UserTicket getTicket() {
231: return getTicket(true);
232: }
233:
234: /**
235: * Gets the user ticket for the current request.
236: *
237: * @param refreshIfDirty true: syncs the ticket with the database if it is dirty
238: * @return the user ticket for the current request.
239: */
240: public static UserTicket getTicket(boolean refreshIfDirty) {
241: FxContext si = FxContext.get();
242: try {
243: Subject sub = getSubject(si);
244: UserTicketImpl ticket;
245: if (sub == null) {
246: ticket = (UserTicketImpl) UserTicketImpl
247: .getGuestTicket();
248: } else {
249: ticket = (UserTicketImpl) FxDefaultLogin
250: .getUserTicket(sub);
251: // Check dirty flag and sync with database if needed
252: if (refreshIfDirty && ticket.isDirty()) {
253: ticket = (UserTicketImpl) getUserTicket(ticket
254: .getLoginName());
255: FxDefaultLogin.updateUserTicket(sub, ticket);
256: storeSubject(sub);
257: }
258: }
259: // Return ticket with system permissions if a runAsSystem flag is set
260: if (si.getRunAsSystem() && !ticket.isGlobalSupervisor()) {
261: ticket = ticket.cloneAsGlobalSupervisor();
262: }
263: return ticket;
264: } catch (Exception exc) {
265: LOG.fatal(exc, exc);
266: return UserTicketImpl.getGuestTicket();
267: }
268: }
269:
270: public static UserTicket getUserTicket(String loginName)
271: throws FxApplicationException {
272: FxContext ri = FxContext.get();
273: ri.runAsSystem();
274: try {
275: AccountEngine ae = EJBLookup.getAccountEngine();
276: Account acc = ae.load(loginName);
277: UserGroupList gl = ae.getGroups(acc.getId());
278: Role roles[] = ae.getRoles(acc.getId(),
279: AccountEngine.RoleLoadMode.ALL);
280: ACLAssignment[] aad = ae
281: .loadAccountAssignments(acc.getId());
282: return new UserTicketImpl(ri.getApplicationId(), ri
283: .isWebDAV(), acc, gl.toLongArray(), roles, aad, acc
284: .getLanguage());
285: } catch (FxNoAccessException exc) {
286: // This should NEVER happen since we are running as system
287: throw new FxLoadException(LOG, exc);
288: } finally {
289: ri.stopRunAsSystem();
290: }
291: }
292:
293: /**
294: * Flags all active UserTickets with a given user id as dirty, which will
295: * force them to sync with the database upon the next access.
296: *
297: * @param userId the user id, or null
298: */
299: public static void flagDirtyHavingUserId(Long userId) {
300: flagDirtyHaving(userId, null, null);
301:
302: }
303:
304: /**
305: * Flags all active UserTickets with a given ACL id as dirty, which will
306: * force them to sync with the database upon the next access.
307: *
308: * @param aclId the acl
309: */
310: public static void flagDirtyHavingACL(long aclId) {
311: flagDirtyHaving(null, null, aclId);
312: }
313:
314: /**
315: * Flags all active UserTickets with a given group id as dirty, which will
316: * force them to sync with the database upon the next access.
317: *
318: * @param groupId the group id
319: */
320: public static void flagDirtyHavingGroupId(Long groupId) {
321: flagDirtyHaving(null, groupId, null);
322: }
323:
324: /**
325: * Flags all active UserTickets with a given id or acl as dirty, which will
326: * force them to sync with the database upon the next access.
327: *
328: * @param userId the user id, or null
329: * @param aclId a acl, or null
330: * @param groupId a group id, or null
331: */
332: private static void flagDirtyHaving(Long userId, Long groupId,
333: Long aclId) {
334: SubjectWithPath[] subs = getSubjects(userId,
335: groupId == null ? null : new long[] { groupId },
336: aclId == null ? null : new long[] { aclId });
337: if (LOG.isDebugEnabled())
338: LOG.debug("Flagging " + subs.length
339: + " dirty subjects with userId=" + userId
340: + ", groupId=" + groupId + ", aclId=" + aclId);
341: for (SubjectWithPath sub : subs) {
342: if (LOG.isDebugEnabled())
343: LOG.debug("Dirty subject: " + sub);
344: try {
345: final UserTicketImpl ticket = (UserTicketImpl) FxDefaultLogin
346: .getUserTicket(sub.subject);
347: if (!ticket.isDirty()) {
348: // Flag as dirty
349: ticket.setDirty(true);
350: FxDefaultLogin
351: .updateUserTicket(sub.subject, ticket);
352: // Write back to the cluster cache
353: storeSubject(sub);
354: }
355: } catch (Exception exc) {
356: LOG.fatal("Subject without UserTicket [" + sub
357: + "], dirty flag update skipped", exc);
358: }
359: }
360: if (LOG.isDebugEnabled())
361: LOG.debug("Done flagging dirty.");
362: if (FxContext.get() != null) {
363: // update current user's ticket
364: FxContext.get().setTicket(getTicket());
365: }
366: }
367:
368: /**
369: * Removes all data matching a given user id.
370: *
371: * @param userId the user id
372: * @param applicationId the application id, may be null for all applications
373: * @return amount of deleted entries
374: */
375: public static int removeUserId(final long userId,
376: final String applicationId) {
377: if (LOG.isDebugEnabled())
378: LOG.debug("Removing userId " + userId
379: + " with applicationId " + applicationId);
380: FxCacheMBean cache = CacheAdmin.getInstance();
381: List<String> remove = new ArrayList<String>(100);
382: // Find all matching nodes
383: try {
384: Set aSet = cache
385: .getChildrenNames(CacheAdmin.ROOT_USERTICKETSTORE);
386: if (aSet == null)
387: return 0;
388: UserTicket aTicket;
389: Subject sub;
390: for (Object subPath : aSet) {
391: String fullNodeName = CacheAdmin.ROOT_USERTICKETSTORE
392: + "/" + subPath;
393: sub = (Subject) cache.get(fullNodeName, KEY_TICKET);
394: if (sub == null)
395: continue;
396: try {
397: aTicket = FxDefaultLogin.getUserTicket(sub);
398: } catch (FxNotFoundException exc) {
399: LOG.error("No UserTicket found in Subject " + sub);
400: continue;
401: }
402: if (aTicket.getUserId() != userId)
403: continue;
404: if (applicationId != null
405: && !aTicket.getApplicationId().equals(
406: applicationId))
407: continue;
408: remove.add(fullNodeName);
409: }
410: } catch (FxCacheException exc) {
411: LOG.error("Failed to examine tickets: " + exc.getMessage(),
412: exc);
413: return 0;
414: }
415: // Clear matching nodes
416: int count = 0;
417: for (String node : remove) {
418: try {
419: cache.remove(node);
420: count++;
421: } catch (Exception exc) {
422: LOG.error("Failed to remove node [" + node
423: + "] for userid:" + userId, exc);
424: }
425: }
426: // Return amount of deleted entries
427: return count;
428: }
429:
430: /**
431: * Returns all UserTickets currently in the store.
432: *
433: * @return all UserTickets that are in the store
434: */
435: public static List<UserTicket> getTickets() {
436: FxCacheMBean cache = CacheAdmin.getInstance();
437: try {
438: List<UserTicket> result = new ArrayList<UserTicket>(1000);
439: Set aSet = cache
440: .getChildrenNames(CacheAdmin.ROOT_USERTICKETSTORE);
441: if (aSet == null)
442: return new ArrayList<UserTicket>(0);
443: UserTicketImpl aTicket;
444: Subject sub;
445: for (Object subPath : aSet) {
446: sub = (Subject) cache
447: .get(CacheAdmin.ROOT_USERTICKETSTORE + "/"
448: + subPath, KEY_TICKET);
449: if (sub == null)
450: continue;
451: try {
452: aTicket = (UserTicketImpl) FxDefaultLogin
453: .getUserTicket(sub);
454: } catch (FxNotFoundException exc) {
455: LOG.error("No UserTicket found in Subject " + sub,
456: exc);
457: continue;
458: }
459: result.add(aTicket);
460: }
461: return result;
462: } catch (FxCacheException exc) {
463: LOG.error("Failed to examine tickets: " + exc.getMessage(),
464: exc);
465: return new ArrayList<UserTicket>(0);
466: }
467: }
468:
469: }
|