001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/authz/tags/sakai_2-4-1/authz-impl/impl/src/java/org/sakaiproject/authz/impl/SakaiSecurity.java $
003: * $Id: SakaiSecurity.java 18600 2006-12-02 21:08:54Z ggolden@umich.edu $
004: ***********************************************************************************
005: *
006: * Copyright (c) 2003, 2004, 2005, 2006 The Sakai Foundation.
007: *
008: * Licensed under the Educational Community License, Version 1.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.opensource.org/licenses/ecl1.php
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: **********************************************************************************/package org.sakaiproject.authz.impl;
021:
022: import java.util.Collection;
023: import java.util.Collections;
024: import java.util.List;
025: import java.util.Stack;
026: import java.util.Vector;
027:
028: import org.apache.commons.logging.Log;
029: import org.apache.commons.logging.LogFactory;
030: import org.sakaiproject.authz.api.AuthzGroupService;
031: import org.sakaiproject.authz.api.SecurityAdvisor;
032: import org.sakaiproject.authz.api.SecurityService;
033: import org.sakaiproject.entity.api.EntityManager;
034: import org.sakaiproject.entity.api.Reference;
035: import org.sakaiproject.memory.api.MemoryService;
036: import org.sakaiproject.memory.api.MultiRefCache;
037: import org.sakaiproject.thread_local.api.ThreadLocalManager;
038: import org.sakaiproject.user.api.User;
039: import org.sakaiproject.user.api.UserDirectoryService;
040:
041: /**
042: * <p>
043: * SakaiSecurity is a Sakai security service.
044: * </p>
045: */
046: public abstract class SakaiSecurity implements SecurityService {
047: /** Our logger. */
048: private static Log M_log = LogFactory.getLog(SakaiSecurity.class);
049:
050: /** A cache of calls to the service and the results. */
051: protected MultiRefCache m_callCache = null;
052:
053: /** ThreadLocalManager key for our SecurityAdvisor Stack. */
054: protected final static String ADVISOR_STACK = "SakaiSecurity.advisor.stack";
055:
056: /**********************************************************************************************************************************************************************************************************************************************************
057: * Dependencies, configuration, and their setter methods
058: *********************************************************************************************************************************************************************************************************************************************************/
059:
060: /**
061: * @return the ThreadLocalManager collaborator.
062: */
063: protected abstract ThreadLocalManager threadLocalManager();
064:
065: /**
066: * @return the AuthzGroupService collaborator.
067: */
068: protected abstract AuthzGroupService authzGroupService();
069:
070: /**
071: * @return the UserDirectoryService collaborator.
072: */
073: protected abstract UserDirectoryService userDirectoryService();
074:
075: /**
076: * @return the MemoryService collaborator.
077: */
078: protected abstract MemoryService memoryService();
079:
080: /**
081: * @return the EntityManager collaborator.
082: */
083: protected abstract EntityManager entityManager();
084:
085: /**********************************************************************************************************************************************************************************************************************************************************
086: * Configuration
087: *********************************************************************************************************************************************************************************************************************************************************/
088:
089: /** The # minutes to cache the security answers. 0 disables the cache. */
090: protected int m_cacheMinutes = 3;
091:
092: /**
093: * Set the # minutes to cache a security answer.
094: *
095: * @param time
096: * The # minutes to cache a security answer (as an integer string).
097: */
098: public void setCacheMinutes(String time) {
099: m_cacheMinutes = Integer.parseInt(time);
100: }
101:
102: /**********************************************************************************************************************************************************************************************************************************************************
103: * Init and Destroy
104: *********************************************************************************************************************************************************************************************************************************************************/
105:
106: /**
107: * Final initialization, once all dependencies are set.
108: */
109: public void init() {
110: // <= 0 minutes indicates no caching desired
111: if (m_cacheMinutes > 0) {
112: // build a synchronized map for the call cache, automatiaclly checking for expiration every 15 mins.
113: m_callCache = memoryService().newMultiRefCache(15 * 60);
114: }
115:
116: M_log.info("init() - caching minutes: " + m_cacheMinutes);
117: }
118:
119: /**
120: * Final cleanup.
121: */
122: public void destroy() {
123: M_log.info("destroy()");
124: }
125:
126: /**********************************************************************************************************************************************************************************************************************************************************
127: * SecurityService implementation
128: *********************************************************************************************************************************************************************************************************************************************************/
129:
130: /**
131: * {@inheritDoc}
132: */
133: public boolean isSuperUser() {
134: User user = userDirectoryService().getCurrentUser();
135: if (user == null)
136: return false;
137:
138: return isSuperUser(user.getId());
139: }
140:
141: /**
142: * {@inheritDoc}
143: */
144: public boolean isSuperUser(String userId) {
145: // if no user or the no-id user (i.e. the anon user)
146: if ((userId == null) || (userId.length() == 0))
147: return false;
148:
149: // check the cache
150: String command = "super@" + userId;
151: if ((m_callCache != null) && (m_callCache.containsKey(command))) {
152: boolean rv = ((Boolean) m_callCache.get(command))
153: .booleanValue();
154: return rv;
155: }
156:
157: boolean rv = false;
158:
159: // these known ids are super
160: if (UserDirectoryService.ADMIN_ID.equalsIgnoreCase(userId)) {
161: rv = true;
162: }
163:
164: else if ("postmaster".equalsIgnoreCase(userId)) {
165: rv = true;
166: }
167:
168: // if the user has site modification rights in the "!admin" site, welcome aboard!
169: else {
170: // TODO: string constants stolen from site -ggolden
171: if (authzGroupService().isAllowed(userId, "site.upd",
172: "/site/!admin")) {
173: rv = true;
174: }
175: }
176:
177: // cache
178: if (m_callCache != null) {
179: Collection azgIds = new Vector();
180: azgIds.add("/site/!admin");
181: m_callCache.put(command, Boolean.valueOf(rv),
182: m_cacheMinutes * 60, null, azgIds);
183: }
184:
185: return rv;
186: }
187:
188: /**
189: * {@inheritDoc}
190: */
191: public boolean unlock(String lock, String resource) {
192: return unlock(userDirectoryService().getCurrentUser(), lock,
193: resource);
194: }
195:
196: /**
197: * {@inheritDoc}
198: */
199: public boolean unlock(User u, String function, String entityRef) {
200: // pick up the current user if needed
201: User user = u;
202: if (user == null) {
203: user = userDirectoryService().getCurrentUser();
204: }
205: return unlock(user.getId(), function, entityRef);
206: }
207:
208: /**
209: * {@inheritDoc}
210: */
211: public boolean unlock(String userId, String function,
212: String entityRef) {
213: return unlock(userId, function, entityRef, null);
214: }
215:
216: /**
217: * {@inheritDoc}
218: */
219: public boolean unlock(String userId, String function,
220: String entityRef, Collection azgs) {
221: // make sure we have complete parameters (azgs is optional)
222: if (userId == null || function == null || entityRef == null) {
223: M_log.warn("unlock(): null: " + userId + " " + function
224: + " " + entityRef);
225: return false;
226: }
227:
228: // if super, grant
229: if (isSuperUser(userId)) {
230: return true;
231: }
232:
233: // let the advisors have a crack at it, if we have any
234: // Note: this cannot be cached without taking into consideration the exact advisor configuration -ggolden
235: if (hasAdvisors()) {
236: SecurityAdvisor.SecurityAdvice advice = adviseIsAllowed(
237: userId, function, entityRef);
238: if (advice != SecurityAdvisor.SecurityAdvice.PASS) {
239: return advice == SecurityAdvisor.SecurityAdvice.ALLOWED;
240: }
241: }
242:
243: // check with the AuthzGroups appropriate for this entity
244: return checkAuthzGroups(userId, function, entityRef, azgs);
245: }
246:
247: /**
248: * Check the appropriate AuthzGroups for the answer - this may be cached
249: *
250: * @param userId
251: * The user id.
252: * @param function
253: * The security function.
254: * @param entityRef
255: * The entity reference string.
256: * @return true if allowed, false if not.
257: */
258: protected boolean checkAuthzGroups(String userId, String function,
259: String entityRef, Collection azgs) {
260: // check the cache
261: String command = "unlock@" + userId + "@" + function + "@"
262: + entityRef;
263: if ((m_callCache != null) && (m_callCache.containsKey(command))) {
264: boolean rv = ((Boolean) m_callCache.get(command))
265: .booleanValue();
266: return rv;
267: }
268:
269: // get this entity's AuthzGroups if needed
270: if (azgs == null) {
271: // make a reference for the entity
272: Reference ref = entityManager().newReference(entityRef);
273:
274: azgs = ref.getAuthzGroups(userId);
275: }
276:
277: boolean rv = authzGroupService().isAllowed(userId, function,
278: azgs);
279:
280: // cache
281: if (m_callCache != null)
282: m_callCache.put(command, Boolean.valueOf(rv),
283: m_cacheMinutes * 60, entityRef, azgs);
284:
285: return rv;
286: }
287:
288: /**
289: * Access the List the Users who can unlock the lock for use with this resource.
290: *
291: * @param lock
292: * The lock id string.
293: * @param reference
294: * The resource reference string.
295: * @return A List (User) of the users can unlock the lock (may be empty).
296: */
297: public List unlockUsers(String lock, String reference) {
298: if (reference == null) {
299: M_log.warn("unlockUsers(): null resource: " + lock);
300: return new Vector();
301: }
302:
303: // make a reference for the resource
304: Reference ref = entityManager().newReference(reference);
305:
306: // get this resource's Realms
307: Collection realms = ref.getAuthzGroups();
308:
309: // get the users who can unlock in these realms
310: List ids = new Vector();
311: ids.addAll(authzGroupService().getUsersIsAllowed(lock, realms));
312:
313: // convert the set of Users into a sorted list of users
314: List users = userDirectoryService().getUsers(ids);
315: Collections.sort(users);
316:
317: return users;
318: }
319:
320: /**********************************************************************************************************************************************************************************************************************************************************
321: * SecurityAdvisor Support
322: *********************************************************************************************************************************************************************************************************************************************************/
323:
324: /**
325: * Get the thread-local security advisor stack, possibly creating it
326: *
327: * @param force
328: * if true, create if missing
329: */
330: protected Stack getAdvisorStack(boolean force) {
331: Stack advisors = (Stack) threadLocalManager()
332: .get(ADVISOR_STACK);
333: if ((advisors == null) && force) {
334: advisors = new Stack();
335: threadLocalManager().set(ADVISOR_STACK, advisors);
336: }
337:
338: return advisors;
339: }
340:
341: /**
342: * Remove the thread-local security advisor stack
343: */
344: protected void dropAdvisorStack() {
345: threadLocalManager().set(ADVISOR_STACK, null);
346: }
347:
348: /**
349: * Check the advisor stack - if anyone declares ALLOWED or NOT_ALLOWED, stop and return that, else, while they PASS, keep checking.
350: *
351: * @param userId
352: * The user id.
353: * @param function
354: * The security function.
355: * @param reference
356: * The Entity reference.
357: * @return ALLOWED or NOT_ALLOWED if an advisor makes a decision, or PASS if there are no advisors or they cannot make a decision.
358: */
359: protected SecurityAdvisor.SecurityAdvice adviseIsAllowed(
360: String userId, String function, String reference) {
361: Stack advisors = getAdvisorStack(false);
362: if ((advisors == null) || (advisors.isEmpty()))
363: return SecurityAdvisor.SecurityAdvice.PASS;
364:
365: // a Stack grows to the right - process from top to bottom
366: for (int i = advisors.size() - 1; i >= 0; i--) {
367: SecurityAdvisor advisor = (SecurityAdvisor) advisors
368: .elementAt(i);
369:
370: SecurityAdvisor.SecurityAdvice advice = advisor.isAllowed(
371: userId, function, reference);
372: if (advice != SecurityAdvisor.SecurityAdvice.PASS) {
373: return advice;
374: }
375: }
376:
377: return SecurityAdvisor.SecurityAdvice.PASS;
378: }
379:
380: /**
381: * @inheritDoc
382: */
383: public void pushAdvisor(SecurityAdvisor advisor) {
384: Stack advisors = getAdvisorStack(true);
385: advisors.push(advisor);
386: }
387:
388: /**
389: * @inheritDoc
390: */
391: public SecurityAdvisor popAdvisor() {
392: Stack advisors = getAdvisorStack(false);
393: if (advisors == null)
394: return null;
395:
396: SecurityAdvisor rv = null;
397:
398: if (advisors.size() > 0) {
399: rv = (SecurityAdvisor) advisors.pop();
400: }
401:
402: if (advisors.isEmpty()) {
403: dropAdvisorStack();
404: }
405:
406: return rv;
407: }
408:
409: /**
410: * @inheritDoc
411: */
412: public boolean hasAdvisors() {
413: Stack advisors = getAdvisorStack(false);
414: if (advisors == null)
415: return false;
416:
417: return !advisors.isEmpty();
418: }
419:
420: /**
421: * @inheritDoc
422: */
423: public void clearAdvisors() {
424: dropAdvisorStack();
425: }
426: }
|