View Javadoc
1   /*
2    * JBoss, Home of Professional Open Source
3    * Copyright 2014, Red Hat, Inc. and/or its affiliates, and individual
4    * contributors by the @authors tag. See the copyright.txt in the
5    * distribution for a full listing of individual contributors.
6    *
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   * http://www.apache.org/licenses/LICENSE-2.0
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.jboss.as.quickstarts.ejb_security_interceptors;
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.net.URL;
22  import java.security.Principal;
23  import java.security.acl.Group;
24  import java.util.Map;
25  import java.util.Properties;
26  
27  import javax.security.auth.Subject;
28  import javax.security.auth.callback.Callback;
29  import javax.security.auth.callback.CallbackHandler;
30  import javax.security.auth.callback.NameCallback;
31  import javax.security.auth.login.LoginException;
32  
33  import org.jboss.security.SimpleGroup;
34  import org.jboss.security.SimplePrincipal;
35  import org.jboss.security.auth.callback.ObjectCallback;
36  import org.jboss.security.auth.spi.AbstractServerLoginModule;
37  
38  /**
39   * Login module to make the decision if one user can ask for the current request to be switched to an alternative specified
40   * user.
41   * 
42   * @author <a href="mailto:darran.lofthouse@jboss.com">Darran Lofthouse</a>
43   */
44  public class DelegationLoginModule extends AbstractServerLoginModule {
45  
46      private static final String DELEGATION_PROPERTIES = "delegationProperties";
47  
48      private static final String DEFAULT_DELEGATION_PROPERTIES = "delegation-mapping.properties";
49  
50      private Properties delegationMappings;
51  
52      private Principal identity;
53  
54      @Override
55      public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
56          addValidOptions(new String[] { DELEGATION_PROPERTIES });
57          super.initialize(subject, callbackHandler, sharedState, options);
58  
59          String propertiesName;
60          if (options.containsKey(DELEGATION_PROPERTIES)) {
61              propertiesName = (String) options.get(DELEGATION_PROPERTIES);
62          } else {
63              propertiesName = DEFAULT_DELEGATION_PROPERTIES;
64          }
65          try {
66              delegationMappings = loadProperties(propertiesName);
67          } catch (IOException e) {
68              throw new IllegalArgumentException(String.format("Unable to load properties '%s'", propertiesName), e);
69          }
70      }
71  
72      @SuppressWarnings("unchecked")
73      @Override
74      public boolean login() throws LoginException {
75          if (super.login() == true) {
76              log.debug("super.login()==true");
77              return true;
78          }
79  
80          // Time to see if this is a delegation request.
81          NameCallback ncb = new NameCallback("Username:");
82          ObjectCallback ocb = new ObjectCallback("Password:");
83  
84          try {
85              callbackHandler.handle(new Callback[] { ncb, ocb });
86          } catch (Exception e) {
87              if (e instanceof RuntimeException) {
88                  throw (RuntimeException) e;
89              }
90              return false; // If the CallbackHandler can not handle the required callbacks then no chance.
91          }
92  
93          String name = ncb.getName();
94          Object credential = ocb.getCredential();
95  
96          if (credential instanceof OuterUserCredential) {
97              // This credential type will only be seen for a delegation request, if not seen then the request is not for us.
98  
99              if (delegationAcceptable(name, (OuterUserCredential) credential)) {
100 
101                 identity = new SimplePrincipal(name);
102                 if (getUseFirstPass()) {
103                     String userName = identity.getName();
104                     if (log.isDebugEnabled())
105                         log.debug("Storing username '" + userName + "' and empty password");
106                     // Add the username and an empty password to the shared state map
107                     sharedState.put("javax.security.auth.login.name", identity);
108                     sharedState.put("javax.security.auth.login.password", "");
109                 }
110                 loginOk = true;
111                 return true;
112             }
113         }
114 
115         return false; // Attempted login but not successful.
116     }
117 
118     /**
119      * Make a trust user to decide if the user switch is acceptable.
120      * 
121      * The default implementation checks the Properties for the user that opened the connection looking for a match, the
122      * property read is then used to check if the connection user can delegate to the user specified.
123      * 
124      * The following entries will be checked in the Properties in this order: - user@realm - This is an exact match for the user
125      * / realm combination of the connection user. user@* - This entry allows a match by username for any realm. *@realm - This
126      * entry allows for any user in the realm specified. * - This matches all users.
127      * 
128      * Once an entry has been found the Properties will not be read again, even if the entry loaded does not allow delegation.
129      * 
130      * The value for the property is either '*' which means delegation to any user is allowed or a comma separate list of users
131      * that can be delegated to.
132      * 
133      * @param requestedUser - The user this request wants to be authorized as.
134      * @param connectionUser - The use of the connection to the server.
135      * @return true if a switch is acceptable, false otherwise.
136      */
137     protected boolean delegationAcceptable(String requestedUser, OuterUserCredential connectionUser) {
138         if (delegationMappings == null) {
139             return false;
140         }
141 
142         String[] allowedMappings = loadPropertyValue(connectionUser.getName(), connectionUser.getRealm());
143         if (allowedMappings.length == 1 && "*".equals(allowedMappings[1])) {
144             // A wild card mapping was found.
145             return true;
146         }
147         for (String current : allowedMappings) {
148             if (requestedUser.equals(current)) {
149                 return true;
150             }
151         }
152 
153         return false;
154     }
155 
156     private String[] loadPropertyValue(final String userName, final String realm) {
157         String value = null;
158 
159         value = delegationMappings.getProperty(userName + "@" + realm);
160         if (value == null) {
161             value = delegationMappings.getProperty(userName + "@*");
162         }
163         if (value == null) {
164             value = delegationMappings.getProperty("*@" + realm);
165         }
166         if (value == null) {
167             value = delegationMappings.getProperty("*");
168         }
169 
170         if (value == null) {
171             return new String[0];
172         } else {
173             return value.split(",");
174         }
175     }
176 
177     @Override
178     protected Principal getIdentity() {
179         return identity;
180     }
181 
182     @Override
183     protected Group[] getRoleSets() throws LoginException {
184         Group roles = new SimpleGroup("Roles");
185         Group callerPrincipal = new SimpleGroup("CallerPrincipal");
186         Group[] groups = { roles, callerPrincipal };
187         callerPrincipal.addMember(getIdentity());
188         return groups;
189     }
190 
191     private Properties loadProperties(final String name) throws IOException {
192         ClassLoader classLoader = SecurityActions.getContextClassLoader();
193         URL url = classLoader.getResource(name);
194         InputStream is = url.openStream();
195         try {
196             Properties props = new Properties();
197             props.load(is);
198             return props;
199 
200         } finally {
201             is.close();
202         }
203     }
204 
205 }