1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.shiro.subject.support;
20
21 import org.apache.shiro.authc.AuthenticationException;
22 import org.apache.shiro.authc.AuthenticationToken;
23 import org.apache.shiro.authc.HostAuthenticationToken;
24 import org.apache.shiro.authz.AuthorizationException;
25 import org.apache.shiro.authz.Permission;
26 import org.apache.shiro.authz.UnauthenticatedException;
27 import org.apache.shiro.mgt.SecurityManager;
28 import org.apache.shiro.session.InvalidSessionException;
29 import org.apache.shiro.session.ProxiedSession;
30 import org.apache.shiro.session.Session;
31 import org.apache.shiro.session.SessionException;
32 import org.apache.shiro.session.mgt.DefaultSessionContext;
33 import org.apache.shiro.session.mgt.SessionContext;
34 import org.apache.shiro.subject.ExecutionException;
35 import org.apache.shiro.subject.PrincipalCollection;
36 import org.apache.shiro.subject.Subject;
37 import org.apache.shiro.util.CollectionUtils;
38 import org.apache.shiro.util.StringUtils;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 import java.util.Collection;
43 import java.util.List;
44 import java.util.concurrent.Callable;
45 import java.util.concurrent.CopyOnWriteArrayList;
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71 public class DelegatingSubject implements Subject {
72
73 private static final Logger log = LoggerFactory.getLogger(DelegatingSubject.class);
74
75 private static final String RUN_AS_PRINCIPALS_SESSION_KEY =
76 DelegatingSubject.class.getName() + ".RUN_AS_PRINCIPALS_SESSION_KEY";
77
78 protected PrincipalCollection principals;
79 protected boolean authenticated;
80 protected String host;
81 protected Session session;
82
83
84
85 protected boolean sessionCreationEnabled;
86
87 protected transient SecurityManager securityManager;
88
89 public DelegatingSubject(SecurityManager securityManager) {
90 this(null, false, null, null, securityManager);
91 }
92
93 public DelegatingSubject(PrincipalCollection principals, boolean authenticated, String host,
94 Session session, SecurityManager securityManager) {
95 this(principals, authenticated, host, session, true, securityManager);
96 }
97
98
99 public DelegatingSubject(PrincipalCollection principals, boolean authenticated, String host,
100 Session session, boolean sessionCreationEnabled, SecurityManager securityManager) {
101 if (securityManager == null) {
102 throw new IllegalArgumentException("SecurityManager argument cannot be null.");
103 }
104 this.securityManager = securityManager;
105 this.principals = principals;
106 this.authenticated = authenticated;
107 this.host = host;
108 if (session != null) {
109 this.session = decorate(session);
110 }
111 this.sessionCreationEnabled = sessionCreationEnabled;
112 }
113
114 protected Session../../../../../org/apache/shiro/session/Session.html#Session">Session decorate(Session session) {
115 if (session == null) {
116 throw new IllegalArgumentException("session cannot be null");
117 }
118 return new StoppingAwareProxiedSession(session, this);
119 }
120
121 public SecurityManager getSecurityManager() {
122 return securityManager;
123 }
124
125 private static boolean isEmpty(PrincipalCollection pc) {
126 return pc == null || pc.isEmpty();
127 }
128
129 protected boolean hasPrincipals() {
130 return !isEmpty(getPrincipals());
131 }
132
133
134
135
136
137
138 public String getHost() {
139 return this.host;
140 }
141
142 private Object getPrimaryPrincipal(PrincipalCollection principals) {
143 if (!isEmpty(principals)) {
144 return principals.getPrimaryPrincipal();
145 }
146 return null;
147 }
148
149
150
151
152 public Object getPrincipal() {
153 return getPrimaryPrincipal(getPrincipals());
154 }
155
156 public PrincipalCollection getPrincipals() {
157 List<PrincipalCollection> runAsPrincipals = getRunAsPrincipalsStack();
158 return CollectionUtils.isEmpty(runAsPrincipals) ? this.principals : runAsPrincipals.get(0);
159 }
160
161 public boolean isPermitted(String permission) {
162 return hasPrincipals() && securityManager.isPermitted(getPrincipals(), permission);
163 }
164
165 public boolean isPermitted(Permission permission) {
166 return hasPrincipals() && securityManager.isPermitted(getPrincipals(), permission);
167 }
168
169 public boolean[] isPermitted(String... permissions) {
170 if (hasPrincipals()) {
171 return securityManager.isPermitted(getPrincipals(), permissions);
172 } else {
173 return new boolean[permissions.length];
174 }
175 }
176
177 public boolean[] isPermitted(List<Permission> permissions) {
178 if (hasPrincipals()) {
179 return securityManager.isPermitted(getPrincipals(), permissions);
180 } else {
181 return new boolean[permissions.size()];
182 }
183 }
184
185 public boolean isPermittedAll(String... permissions) {
186 return hasPrincipals() && securityManager.isPermittedAll(getPrincipals(), permissions);
187 }
188
189 public boolean isPermittedAll(Collection<Permission> permissions) {
190 return hasPrincipals() && securityManager.isPermittedAll(getPrincipals(), permissions);
191 }
192
193 protected void assertAuthzCheckPossible() throws AuthorizationException {
194 if (!hasPrincipals()) {
195 String msg = "This subject is anonymous - it does not have any identifying principals and " +
196 "authorization operations require an identity to check against. A Subject instance will " +
197 "acquire these identifying principals automatically after a successful login is performed " +
198 "be executing " + Subject.class.getName() + ".login(AuthenticationToken) or when 'Remember Me' " +
199 "functionality is enabled by the SecurityManager. This exception can also occur when a " +
200 "previously logged-in Subject has logged out which " +
201 "makes it anonymous again. Because an identity is currently not known due to any of these " +
202 "conditions, authorization is denied.";
203 throw new UnauthenticatedException(msg);
204 }
205 }
206
207 public void checkPermission(String permission) throws AuthorizationException {
208 assertAuthzCheckPossible();
209 securityManager.checkPermission(getPrincipals(), permission);
210 }
211
212 public void checkPermission(Permission permission) throws AuthorizationException {
213 assertAuthzCheckPossible();
214 securityManager.checkPermission(getPrincipals(), permission);
215 }
216
217 public void checkPermissions(String... permissions) throws AuthorizationException {
218 assertAuthzCheckPossible();
219 securityManager.checkPermissions(getPrincipals(), permissions);
220 }
221
222 public void checkPermissions(Collection<Permission> permissions) throws AuthorizationException {
223 assertAuthzCheckPossible();
224 securityManager.checkPermissions(getPrincipals(), permissions);
225 }
226
227 public boolean hasRole(String roleIdentifier) {
228 return hasPrincipals() && securityManager.hasRole(getPrincipals(), roleIdentifier);
229 }
230
231 public boolean[] hasRoles(List<String> roleIdentifiers) {
232 if (hasPrincipals()) {
233 return securityManager.hasRoles(getPrincipals(), roleIdentifiers);
234 } else {
235 return new boolean[roleIdentifiers.size()];
236 }
237 }
238
239 public boolean hasAllRoles(Collection<String> roleIdentifiers) {
240 return hasPrincipals() && securityManager.hasAllRoles(getPrincipals(), roleIdentifiers);
241 }
242
243 public void checkRole(String role) throws AuthorizationException {
244 assertAuthzCheckPossible();
245 securityManager.checkRole(getPrincipals(), role);
246 }
247
248 public void checkRoles(String... roleIdentifiers) throws AuthorizationException {
249 assertAuthzCheckPossible();
250 securityManager.checkRoles(getPrincipals(), roleIdentifiers);
251 }
252
253 public void checkRoles(Collection<String> roles) throws AuthorizationException {
254 assertAuthzCheckPossible();
255 securityManager.checkRoles(getPrincipals(), roles);
256 }
257
258 public void login(AuthenticationToken token) throws AuthenticationException {
259 clearRunAsIdentitiesInternal();
260 Subject subject = securityManager.login(this, token);
261
262 PrincipalCollection principals;
263
264 String host = null;
265
266 if (subject instanceof DelegatingSubject) {
267 DelegatingSubjectorg/apache/shiro/subject/support/DelegatingSubject.html#DelegatingSubject">DelegatingSubject delegating = (DelegatingSubject) subject;
268
269 principals = delegating.principals;
270 host = delegating.host;
271 } else {
272 principals = subject.getPrincipals();
273 }
274
275 if (principals == null || principals.isEmpty()) {
276 String msg = "Principals returned from securityManager.login( token ) returned a null or " +
277 "empty value. This value must be non null and populated with one or more elements.";
278 throw new IllegalStateException(msg);
279 }
280 this.principals = principals;
281 this.authenticated = true;
282 if (token instanceof HostAuthenticationToken) {
283 host = ((HostAuthenticationToken) token).getHost();
284 }
285 if (host != null) {
286 this.host = host;
287 }
288 Session session = subject.getSession(false);
289 if (session != null) {
290 this.session = decorate(session);
291 } else {
292 this.session = null;
293 }
294 }
295
296 public boolean isAuthenticated() {
297 return authenticated && hasPrincipals();
298 }
299
300 public boolean isRemembered() {
301 PrincipalCollection principals = getPrincipals();
302 return principals != null && !principals.isEmpty() && !isAuthenticated();
303 }
304
305
306
307
308
309
310
311 protected boolean isSessionCreationEnabled() {
312 return this.sessionCreationEnabled;
313 }
314
315 public Session getSession() {
316 return getSession(true);
317 }
318
319 public Session getSession(boolean create) {
320 if (log.isTraceEnabled()) {
321 log.trace("attempting to get session; create = " + create +
322 "; session is null = " + (this.session == null) +
323 "; session has id = " + (this.session != null && session.getId() != null));
324 }
325
326 if (this.session == null && create) {
327
328
329 if (!isSessionCreationEnabled()) {
330 String msg = "Session creation has been disabled for the current subject. This exception indicates " +
331 "that there is either a programming error (using a session when it should never be " +
332 "used) or that Shiro's configuration needs to be adjusted to allow Sessions to be created " +
333 "for the current Subject. See the " + DisabledSessionException.class.getName() + " JavaDoc " +
334 "for more.";
335 throw new DisabledSessionException(msg);
336 }
337
338 log.trace("Starting session for host {}", getHost());
339 SessionContext sessionContext = createSessionContext();
340 Session session = this.securityManager.start(sessionContext);
341 this.session = decorate(session);
342 }
343 return this.session;
344 }
345
346 protected SessionContext createSessionContext() {
347 SessionContext sessionContext = new DefaultSessionContext();
348 if (StringUtils.hasText(host)) {
349 sessionContext.setHost(host);
350 }
351 return sessionContext;
352 }
353
354 private void clearRunAsIdentitiesInternal() {
355
356 try {
357 clearRunAsIdentities();
358 } catch (SessionException se) {
359 log.debug("Encountered session exception trying to clear 'runAs' identities during logout. This " +
360 "can generally safely be ignored.", se);
361 }
362 }
363
364 public void logout() {
365 try {
366 clearRunAsIdentitiesInternal();
367 this.securityManager.logout(this);
368 } finally {
369 this.session = null;
370 this.principals = null;
371 this.authenticated = false;
372
373
374
375
376
377 }
378 }
379
380 private void sessionStopped() {
381 this.session = null;
382 }
383
384 public <V> V execute(Callable<V> callable) throws ExecutionException {
385 Callable<V> associated = associateWith(callable);
386 try {
387 return associated.call();
388 } catch (Throwable t) {
389 throw new ExecutionException(t);
390 }
391 }
392
393 public void execute(Runnable runnable) {
394 Runnable associated = associateWith(runnable);
395 associated.run();
396 }
397
398 public <V> Callable<V> associateWith(Callable<V> callable) {
399 return new SubjectCallable<V>(this, callable);
400 }
401
402 public Runnable associateWith(Runnable runnable) {
403 if (runnable instanceof Thread) {
404 String msg = "This implementation does not support Thread arguments because of JDK ThreadLocal " +
405 "inheritance mechanisms required by Shiro. Instead, the method argument should be a non-Thread " +
406 "Runnable and the return value from this method can then be given to an ExecutorService or " +
407 "another Thread.";
408 throw new UnsupportedOperationException(msg);
409 }
410 return new SubjectRunnable(this, runnable);
411 }
412
413 private class StoppingAwareProxiedSession extends ProxiedSession {
414
415 private final DelegatingSubject owner;
416
417 private StoppingAwareProxiedSession(Session target, DelegatingSubject owningSubject) {
418 super(target);
419 owner = owningSubject;
420 }
421
422 public void stop() throws InvalidSessionException {
423 super.stop();
424 owner.sessionStopped();
425 }
426 }
427
428
429
430
431
432
433 public void runAs(PrincipalCollection principals) {
434 if (!hasPrincipals()) {
435 String msg = "This subject does not yet have an identity. Assuming the identity of another " +
436 "Subject is only allowed for Subjects with an existing identity. Try logging this subject in " +
437 "first, or using the " + Subject.Builder.class.getName() + " to build ad hoc Subject instances " +
438 "with identities as necessary.";
439 throw new IllegalStateException(msg);
440 }
441 pushIdentity(principals);
442 }
443
444 public boolean isRunAs() {
445 List<PrincipalCollection> stack = getRunAsPrincipalsStack();
446 return !CollectionUtils.isEmpty(stack);
447 }
448
449 public PrincipalCollection getPreviousPrincipals() {
450 PrincipalCollection previousPrincipals = null;
451 List<PrincipalCollection> stack = getRunAsPrincipalsStack();
452 int stackSize = stack != null ? stack.size() : 0;
453 if (stackSize > 0) {
454 if (stackSize == 1) {
455 previousPrincipals = this.principals;
456 } else {
457
458 assert stack != null;
459 previousPrincipals = stack.get(1);
460 }
461 }
462 return previousPrincipals;
463 }
464
465 public PrincipalCollection releaseRunAs() {
466 return popIdentity();
467 }
468
469 @SuppressWarnings("unchecked")
470 private List<PrincipalCollection> getRunAsPrincipalsStack() {
471 Session session = getSession(false);
472 if (session != null) {
473 return (List<PrincipalCollection>) session.getAttribute(RUN_AS_PRINCIPALS_SESSION_KEY);
474 }
475 return null;
476 }
477
478 private void clearRunAsIdentities() {
479 Session session = getSession(false);
480 if (session != null) {
481 session.removeAttribute(RUN_AS_PRINCIPALS_SESSION_KEY);
482 }
483 }
484
485 private void pushIdentity(PrincipalCollection principals) throws NullPointerException {
486 if (isEmpty(principals)) {
487 String msg = "Specified Subject principals cannot be null or empty for 'run as' functionality.";
488 throw new NullPointerException(msg);
489 }
490 List<PrincipalCollection> stack = getRunAsPrincipalsStack();
491 if (stack == null) {
492 stack = new CopyOnWriteArrayList<PrincipalCollection>();
493 }
494 stack.add(0, principals);
495 Session session = getSession();
496 session.setAttribute(RUN_AS_PRINCIPALS_SESSION_KEY, stack);
497 }
498
499 private PrincipalCollection popIdentity() {
500 PrincipalCollection popped = null;
501
502 List<PrincipalCollection> stack = getRunAsPrincipalsStack();
503 if (!CollectionUtils.isEmpty(stack)) {
504 popped = stack.remove(0);
505 Session session;
506 if (!CollectionUtils.isEmpty(stack)) {
507
508 session = getSession();
509 session.setAttribute(RUN_AS_PRINCIPALS_SESSION_KEY, stack);
510 } else {
511
512 clearRunAsIdentities();
513 }
514 }
515
516 return popped;
517 }
518 }