1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
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.apache.catalina.realm;
18
19 import java.security.Principal;
20 import java.util.ArrayList;
21 import java.util.Iterator;
22 import java.util.List;
23
24 import javax.naming.Context;
25
26 import org.apache.catalina.Group;
27 import org.apache.catalina.LifecycleException;
28 import org.apache.catalina.Role;
29 import org.apache.catalina.User;
30 import org.apache.catalina.UserDatabase;
31 import org.apache.catalina.Wrapper;
32 import org.apache.tomcat.util.ExceptionUtils;
33
34 /**
35  * Implementation of {@link org.apache.catalina.Realm} that is based on an
36  * implementation of {@link UserDatabase} made available through the global JNDI
37  * resources configured for this instance of Catalina. Set the
38  * <code>resourceName</code> parameter to the global JNDI resources name for the
39  * configured instance of <code>UserDatabase</code> that we should consult.
40  *
41  * @author Craig R. McClanahan
42  * @since 4.1
43  */

44 public class UserDatabaseRealm extends RealmBase {
45
46     // ----------------------------------------------------- Instance Variables
47
48     /**
49      * The <code>UserDatabase</code> we will use to authenticate users and
50      * identify associated roles.
51      */

52     protected UserDatabase database = null;
53
54     /**
55      * The global JNDI name of the <code>UserDatabase</code> resource we will be
56      * utilizing.
57      */

58     protected String resourceName = "UserDatabase";
59
60
61     // ------------------------------------------------------------- Properties
62
63     /**
64      * @return the global JNDI name of the <code>UserDatabase</code> resource we
65      *         will be using.
66      */

67     public String getResourceName() {
68         return resourceName;
69     }
70
71
72     /**
73      * Set the global JNDI name of the <code>UserDatabase</code> resource we
74      * will be using.
75      *
76      * @param resourceName The new global JNDI name
77      */

78     public void setResourceName(String resourceName) {
79         this.resourceName = resourceName;
80     }
81
82
83     // --------------------------------------------------------- Public Methods
84
85     /**
86      * Return <code>true</code> if the specified Principal has the specified
87      * security role, within the context of this Realm; otherwise return
88      * <code>false</code>. This implementation returns <code>true</code> if the
89      * <code>User</code> has the role, or if any <code>Group</code> that the
90      * <code>User</code> is a member of has the role.
91      *
92      * @param principal Principal for whom the role is to be checked
93      * @param role Security role to be checked
94      */

95     @Override
96     public boolean hasRole(Wrapper wrapper, Principal principal, String role) {
97         // Check for a role alias defined in a <security-role-ref> element
98         if (wrapper != null) {
99             String realRole = wrapper.findSecurityReference(role);
100             if (realRole != null)
101                 role = realRole;
102         }
103         if (principal instanceof GenericPrincipal) {
104             GenericPrincipal gp = (GenericPrincipal) principal;
105             if (gp.getUserPrincipal() instanceof User) {
106                 principal = gp.getUserPrincipal();
107             }
108         }
109         if (!(principal instanceof User)) {
110             // Play nice with SSO and mixed Realms
111             // No need to pass the wrapper here because role mapping has been
112             // performed already a few lines above
113             return super.hasRole(null, principal, role);
114         }
115         if ("*".equals(role)) {
116             return true;
117         } else if (role == null) {
118             return false;
119         }
120         User user = (User) principal;
121         Role dbrole = database.findRole(role);
122         if (dbrole == null) {
123             return false;
124         }
125         if (user.isInRole(dbrole)) {
126             return true;
127         }
128         Iterator<Group> groups = user.getGroups();
129         while (groups.hasNext()) {
130             Group group = groups.next();
131             if (group.isInRole(dbrole)) {
132                 return true;
133             }
134         }
135         return false;
136     }
137
138
139     // ------------------------------------------------------ Protected Methods
140
141     @Override
142     public void backgroundProcess() {
143         database.backgroundProcess();
144     }
145
146
147     /**
148      * Return the password associated with the given principal's user name.
149      */

150     @Override
151     protected String getPassword(String username) {
152         User user = database.findUser(username);
153
154         if (user == null) {
155             return null;
156         }
157
158         return user.getPassword();
159     }
160
161
162     /**
163      * Return the Principal associated with the given user name.
164      */

165     @Override
166     protected Principal getPrincipal(String username) {
167
168         User user = database.findUser(username);
169         if (user == null) {
170             return null;
171         }
172
173         List<String> roles = new ArrayList<>();
174         Iterator<Role> uroles = user.getRoles();
175         while (uroles.hasNext()) {
176             Role role = uroles.next();
177             roles.add(role.getName());
178         }
179         Iterator<Group> groups = user.getGroups();
180         while (groups.hasNext()) {
181             Group group = groups.next();
182             uroles = group.getRoles();
183             while (uroles.hasNext()) {
184                 Role role = uroles.next();
185                 roles.add(role.getName());
186             }
187         }
188         return new GenericPrincipal(username, user.getPassword(), roles, user);
189     }
190
191
192     // ------------------------------------------------------ Lifecycle Methods
193
194     /**
195      * Prepare for the beginning of active use of the public methods of this
196      * component and implement the requirements of
197      * {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
198      *
199      * @exception LifecycleException if this component detects a fatal error
200      *                that prevents this component from being used
201      */

202     @Override
203     protected void startInternal() throws LifecycleException {
204
205         try {
206             Context context = getServer().getGlobalNamingContext();
207             database = (UserDatabase) context.lookup(resourceName);
208         } catch (Throwable e) {
209             ExceptionUtils.handleThrowable(e);
210             containerLog.error(sm.getString("userDatabaseRealm.lookup", resourceName), e);
211             database = null;
212         }
213         if (database == null) {
214             throw new LifecycleException(
215                     sm.getString("userDatabaseRealm.noDatabase", resourceName));
216         }
217
218         super.startInternal();
219     }
220
221
222     /**
223      * Gracefully terminate the active use of the public methods of this
224      * component and implement the requirements of
225      * {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
226      *
227      * @exception LifecycleException if this component detects a fatal error
228      *                that needs to be reported
229      */

230     @Override
231     protected void stopInternal() throws LifecycleException {
232
233         // Perform normal superclass finalization
234         super.stopInternal();
235
236         // Release reference to our user database
237         database = null;
238     }
239 }
240