001: /**
002: * EasyBeans
003: * Copyright (C) 2006 Bull S.A.S.
004: * Contact: easybeans@ow2.org
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
019: * USA
020: *
021: * --------------------------------------------------------------------------
022: * $Id: SecurityContext.java 1970 2007-10-16 11:49:25Z benoitf $
023: * --------------------------------------------------------------------------
024: */package org.ow2.easybeans.security.propagation.context;
025:
026: import java.io.Serializable;
027: import java.security.Principal;
028: import java.security.acl.Group;
029: import java.util.ArrayList;
030: import java.util.Collections;
031: import java.util.List;
032:
033: import javax.security.auth.Subject;
034:
035: import org.ow2.easybeans.security.api.EZBSecurityContext;
036: import org.ow2.easybeans.security.struct.JGroup;
037: import org.ow2.easybeans.security.struct.JPrincipal;
038: import org.ow2.util.log.Log;
039: import org.ow2.util.log.LogFactory;
040:
041: /**
042: * Security Context that is exchanged and propagated from clients to beans.<br>
043: * This is also why it is a serializable object (as it has to be exchanged).<br>
044: * The security contains allow to get the current principal and the roles
045: * associated to this principal.<br>
046: * RunAs mode is managed by keeping the previous security context.
047: * @author Florent Benoit
048: */
049: public final class SecurityContext implements EZBSecurityContext,
050: Serializable {
051:
052: /**
053: * UID for serialization.
054: */
055: private static final long serialVersionUID = 6612085599241360430L;
056:
057: /**
058: * Logger.
059: */
060: private static Log logger = LogFactory
061: .getLog(SecurityContext.class);
062:
063: /**
064: * Anonymous user name.
065: */
066: private static final String ANONYMOUS_USER = "EasyBeans/Anonymous";
067:
068: /**
069: * Anonymous role.
070: */
071: private static final String ANONYMOUS_ROLE = "anonymous";
072:
073: /**
074: * Anonymous subject (not authenticated).
075: */
076: private static final Subject ANONYMOUS_SUBJECT = buildAnonymousSubject();
077:
078: /**
079: * Current subject (subject that has been authenticated).<br>
080: * By default, it is the anonymous subject.
081: */
082: private Subject subject = ANONYMOUS_SUBJECT;
083:
084: /**
085: * caller subject in run-as mode<br>
086: * In run-as case, the run-as subject is set as the current subject, and the
087: * previous one is kept.<br>
088: * This previous subject is used to get the caller on the run-as bean.
089: */
090: private Subject callerInRunAsModeSubject = null;
091:
092: /**
093: * Default private constructor.
094: */
095: public SecurityContext() {
096:
097: }
098:
099: /**
100: * Build a security context with the given subject.
101: * @param subject the given subject.
102: */
103: public SecurityContext(final Subject subject) {
104: this .subject = subject;
105: }
106:
107: /**
108: * Enters in run-as mode with the given subject.<br>
109: * The previous subject is stored and will be restored when run-as mode will
110: * be ended.
111: * @param runAsSubject the subject to used in run-as mode.
112: * @return the previous subject.
113: */
114: public Subject enterRunAs(final Subject runAsSubject) {
115: // keep previous
116: this .callerInRunAsModeSubject = subject;
117:
118: // update the new one
119: this .subject = runAsSubject;
120:
121: // return previous.
122: return callerInRunAsModeSubject;
123: }
124:
125: /**
126: * Ends the run-as mode and then restore the context stored by container.
127: * @param oldSubject subject kept by container and restored.
128: */
129: public void endsRunAs(final Subject oldSubject) {
130: this .subject = oldSubject;
131:
132: // cancel caller of run-as subject (run-as mode has ended)
133: this .callerInRunAsModeSubject = null;
134: }
135:
136: /**
137: * Gets the caller's principal.
138: * @param runAsBean if true, the bean is a run-as bean.
139: * @return principal of the caller.
140: */
141: public Principal getCallerPrincipal(final boolean runAsBean) {
142: Subject subject = null;
143:
144: // in run-as mode, needs to return callerInRunAsModeSubject's principal.
145: if (runAsBean && callerInRunAsModeSubject != null) {
146: subject = this .callerInRunAsModeSubject;
147: } else {
148: subject = this .subject;
149: }
150:
151: // Then, takes the first principal found. (which is not a role)
152: for (Principal principal : subject
153: .getPrincipals(Principal.class)) {
154: if (!(principal instanceof Group)) {
155: return principal;
156: }
157: }
158:
159: // Principal was not found, severe problem as it should be there. Maybe
160: // the subject was not built correctly.
161: logger
162: .error("No principal found in the current subject. Authentication should have failed when populating subject");
163: throw new IllegalStateException(
164: "No principal found in the current subject. Authentication should have failed when populating subject");
165: }
166:
167: /**
168: * Gets the caller's roles.
169: * @param runAsBean if true, the bean is a run-as bean.
170: * @return list of roles of the caller.
171: */
172: public List<? extends Principal> getCallerRolesList(
173: final boolean runAsBean) {
174: Subject subject = null;
175:
176: // in run-as mode, needs to return callerInRunAsModeSubject's principal.
177: if (runAsBean && callerInRunAsModeSubject != null) {
178: subject = this .callerInRunAsModeSubject;
179: } else {
180: subject = this .subject;
181: }
182:
183: // Then, takes all the roles found in this principal.
184: for (Principal principal : subject
185: .getPrincipals(Principal.class)) {
186: if (principal instanceof Group) {
187: return Collections.list(((Group) principal).members());
188: }
189: }
190:
191: // Principal was not found, severe problem as it should be there. Maybe
192: // the subject was not built correctly.
193: logger
194: .error("No role found in the current subject. Authentication should have failed when populating subject");
195: throw new IllegalStateException(
196: "No role found in the current subject. Authentication should have failed when populating subject");
197: }
198:
199: /**
200: * Gets the caller's roles.
201: * @param runAsBean if true, the bean is a run-as bean.
202: * @return array of roles of the caller.
203: */
204: public Principal[] getCallerRoles(final boolean runAsBean) {
205: List<? extends Principal> callerRoles = getCallerRolesList(runAsBean);
206: return callerRoles.toArray(new Principal[callerRoles.size()]);
207: }
208:
209: /**
210: * Build an anonymous subject when no user is authenticated.<br>
211: * This is required as getCallerPrincipal() should never return null.
212: * @return anonymous subject.
213: */
214: private static Subject buildAnonymousSubject() {
215: return buildSubject(ANONYMOUS_USER,
216: new String[] { ANONYMOUS_ROLE });
217: }
218:
219: /**
220: * Build a subject with the given user name and the list of roles.<br>
221: * @param userName given username
222: * @param roleArray given array of roles.
223: * @return built subject.
224: */
225: public static Subject buildSubject(final String userName,
226: final String[] roleArray) {
227: List<String> roles = new ArrayList<String>();
228: if (roleArray != null) {
229: for (String role : roleArray) {
230: roles.add(role);
231: }
232: }
233: return buildSubject(userName, roles);
234: }
235:
236: /**
237: * Build a subject with the given user name and the list of roles.<br>
238: * @param userName given username
239: * @param roleList given list of roles.
240: * @return built subject.
241: */
242: public static Subject buildSubject(final String userName,
243: final List<String> roleList) {
244: Subject subject = new Subject();
245:
246: // Add principal name
247: Principal principalName = new JPrincipal(userName);
248: subject.getPrincipals().add(principalName);
249:
250: // Add roles for this principal
251: Group roles = new JGroup("roles");
252: if (roleList != null) {
253: for (String role : roleList) {
254: roles.addMember(new JPrincipal(role));
255: }
256: }
257: subject.getPrincipals().add(roles);
258:
259: return subject;
260: }
261:
262: }
|