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