1 /* Copyright (c) 2001-2014, The HSQL Development Group 2 * All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are met: 6 * 7 * Redistributions of source code must retain the above copyright notice, this 8 * list of conditions and the following disclaimer. 9 * 10 * Redistributions in binary form must reproduce the above copyright notice, 11 * this list of conditions and the following disclaimer in the documentation 12 * and/or other materials provided with the distribution. 13 * 14 * Neither the name of the HSQL Development Group nor the names of its 15 * contributors may be used to endorse or promote products derived from this 16 * software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG, 22 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 23 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 24 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 32 package org.hsqldb.auth; 33 34 import java.security.Principal; 35 import java.util.ArrayList; 36 import java.util.List; 37 import java.util.regex.Matcher; 38 import java.util.regex.Pattern; 39 import javax.security.auth.Subject; 40 import javax.security.auth.callback.Callback; 41 import javax.security.auth.callback.CallbackHandler; 42 import javax.security.auth.callback.NameCallback; 43 import javax.security.auth.callback.PasswordCallback; 44 import javax.security.auth.callback.UnsupportedCallbackException; 45 import javax.security.auth.login.LoginContext; 46 import javax.security.auth.login.LoginException; 47 48 import org.hsqldb.lib.FrameworkLogger; 49 50 /** 51 * Provides authentication and authorization (roles and initial schema) 52 * according to JAAS modules configured by the runtime JAAS implementation. 53 * <P> 54 * <b>JAAS modules used must have both a NameCallback and a PasswordCallback.</b> 55 * This is how we pass the JDBC-provided user name and password to the module. 56 * </P> <P> 57 * JAAS setup is Java-implementation-specific. 58 * For Sun Java, you set up a JAAS configuration file which resides at 59 * <code>$HOME/.java.login.config</code> or at the location that you set with 60 * Java system property <code>java.security.auth.login.config</code>. 61 * </P> <P> 62 * You can use this bean to manage just access, or also to manage roles or 63 * initial schemas. 64 * To use for roles or initial schemas, you must set the roleSchemaValuePattern 65 * property to distinguish which of the JAAS-module-provided values to use. 66 * By default, all JAAS-module-provided Principles will be candidates. 67 * If you set property roleSchemaViaCredential to true, then all 68 * JAAS-module-provided public Credentials will be candidates instead. 69 * </P> 70 * 71 * @see AuthFunctionBean 72 * @see NameCallback 73 * @see PasswordCallback 74 * @author Blaine Simpson (blaine dot simpson at admc dot com) 75 * @since 2.0.1 76 */ 77 public class JaasAuthBean implements AuthFunctionBean { 78 private static FrameworkLogger logger = 79 FrameworkLogger.getLog(JaasAuthBean.class); 80 81 private boolean initialized; 82 private String applicationKey; 83 private Pattern roleSchemaValuePattern; 84 private boolean roleSchemaViaCredential; 85 JaasAuthBean()86 public JaasAuthBean() { 87 // Intentionally empty 88 } 89 90 /** 91 * By default, If roleSchemaValuePattern is set, then role and schema 92 * values are obtained from principle values; otherwise existing account 93 * privileges are used (if any). 94 * If roleSchemaViaCredential is set to true and roleSchemaValuePattern is 95 * set, then credential values will be used instead. 96 * <P> 97 * Do not set roleSchemaViaCredential to true unless roleSchemaValuePattern 98 * is set. 99 * </P> 100 */ setRoleSchemaViaCredential(boolean roleSchemaViaCredential)101 public void setRoleSchemaViaCredential(boolean roleSchemaViaCredential) { 102 this.roleSchemaViaCredential = roleSchemaViaCredential; 103 } 104 105 /** 106 * @throws IllegalStateException if any required setting has not been set. 107 */ init()108 public void init() { 109 if (applicationKey == null) { 110 throw new IllegalStateException( 111 "Required property 'applicationKey' not set"); 112 } 113 if (roleSchemaViaCredential && roleSchemaValuePattern == null) { 114 throw new IllegalStateException( 115 "Properties 'roleSchemaViaCredential' and " 116 + "'roleSchemaValuePattern' are mutually exclusive. " 117 + "If you want JaasAuthBean to manage roles or schemas, " 118 + "you must set property 'roleSchemaValuePattern'."); 119 } 120 initialized = true; 121 } 122 123 /** 124 * Set the key into the JAAS runtime configuration. 125 * 126 * For Sun's JAAS implementation, this is the "application" identifier for 127 * a stanza in the JAAS configuration file. 128 */ setApplicationKey(String applicationKey)129 public void setApplicationKey(String applicationKey) { 130 this.applicationKey = applicationKey; 131 } 132 133 /** 134 * Assign a pattern to both detect honored values, and optionally 135 * to map from a single principal name or public credential string 136 * to a single HyperSQL role or schema string. 137 * Do not use this method if you are using this JaasAuthBean only to 138 * permit or reject access (with roles and schema being determined by 139 * pre-existing local HyperSQL accounts). 140 * On that case, simple success of the login() method method will allow 141 * access as the specified user. 142 * <P> 143 * If every principal name or public credentials holds only the String 144 * values precisely as HyperSQL needs them, then set the pattern to ".+". 145 * For example, if the JAAS module returns principals (or credentials) with 146 * values "one", "two", "three", then if you set this pattern to ".+", 147 * HyperSQL will attempt to assign initial schema and roles for the values 148 * "one", "two", and "three". 149 * </P><P> 150 * These are two distinct and important purposes for the specified Pattern. 151 * <OL> 152 * <LI> 153 * Values that do not successfully match the pattern will be ignored. 154 * If the pattern does match, then the entire principal or credential 155 * value will be used to assign initial schema or role (as long as it 156 * is a valid schema name or role name in the local database). 157 * <LI> 158 * Optionally uses parentheses to specify a single capture group 159 * (if you use parentheses to specify more than one matching group, we 160 * will only capture for the first). 161 * What is captured by this group is exactly the role or schema that 162 * HyperSQL will attempt to assign. 163 * If no capture parens are given then the Pattern is only used for the 164 * acceptance decision, and the JAAS-provided value will be returned 165 * verbatim. 166 * </OL> 167 * </P><P> 168 * N.b. this Pattern will be used for the matches() operation, therefore it 169 * must match the entire candidate value strings (this is different than 170 * the find operation which does not need to satisfy the entire candidate 171 * value). 172 * </P><P>Example1 :<CODE><PRE> 173 * cn=([^,]+),ou=dbRole,dc=admc,dc=com 174 * </PRE></CODE> 175 * will extract the CN value from matching attribute values. 176 * </P><P>Example1 :<CODE><PRE> 177 * cn=[^,]+,ou=dbRole,dc=admc,dc=com 178 * </PRE></CODE> 179 * will return the entire <CODE>cn...com</CODE> string for matching 180 * attribute values. 181 * </P> 182 * 183 * @see Matcher#matches() 184 */ setRoleSchemaValuePattern(Pattern roleSchemaValuePattern)185 public void setRoleSchemaValuePattern(Pattern roleSchemaValuePattern) { 186 this.roleSchemaValuePattern = roleSchemaValuePattern; 187 } 188 189 /** 190 * String wrapper for method setRoleSchemaValuePattern(Pattern) 191 * 192 * Use the (x?) Pattern constructs to set options. 193 * 194 * @throws java.util.regex.PatternSyntaxException 195 * @see #setRoleSchemaValuePattern(Pattern) 196 */ setRoleSchemaValuePatternString(String patternString)197 public void setRoleSchemaValuePatternString(String patternString) { 198 setRoleSchemaValuePattern(Pattern.compile(patternString)); 199 } 200 201 public static class UPCallbackHandler implements CallbackHandler { 202 private String u; 203 private char[] p; 204 UPCallbackHandler(String u, String pString)205 public UPCallbackHandler(String u, String pString) { 206 this.u = u; 207 p = pString.toCharArray(); 208 } 209 handle(Callback[] callbacks)210 public void handle(Callback[] callbacks) 211 throws UnsupportedCallbackException { 212 boolean didSetName = false; 213 boolean didSetPassword = false; 214 for (Callback cb : callbacks) 215 if (cb instanceof NameCallback) { 216 ((NameCallback) cb).setName(u); 217 didSetName = true; 218 } else if (cb instanceof PasswordCallback) { 219 ((PasswordCallback) cb).setPassword(p); 220 didSetPassword = true; 221 } else { 222 throw new UnsupportedCallbackException(cb, 223 "Unsupported Callback type: " 224 + cb.getClass().getName()); 225 } 226 if (!didSetName) 227 throw new IllegalStateException( 228 "Supplied Callbacks does not include a NameCallback"); 229 if (!didSetPassword) 230 throw new IllegalStateException("Supplied Callbacks " 231 + "does not include a PasswordCallback"); 232 } 233 } 234 235 /** 236 * @see AuthFunctionBean#authenticate(String, String) 237 */ authenticate(String userName, String password)238 public String[] authenticate(String userName, String password) 239 throws DenyException { 240 if (!initialized) { 241 throw new IllegalStateException( 242 "You must invoke the 'init' method to initialize the " 243 + JaasAuthBean.class.getName() + " instance."); 244 } 245 try { 246 LoginContext lc = 247 new LoginContext(applicationKey, 248 new UPCallbackHandler(userName, password)); 249 try { 250 lc.login(); 251 } catch (LoginException le) { 252 // I wish there were a way to distinguish system problems from 253 // purposeful rejections here. :-( 254 logger.finer("JSSE backend denying access: " + le); 255 throw new DenyException(); 256 } 257 try { 258 if (roleSchemaValuePattern == null) { 259 return null; 260 } 261 int i = 0; 262 Matcher m = null; 263 List<String> rsCandidates = new ArrayList<String>(); 264 List<String> rsList = new ArrayList<String>(); 265 Subject s = lc.getSubject(); 266 if (roleSchemaViaCredential) { 267 for (Object cred : 268 new ArrayList(s.getPublicCredentials())) { 269 rsCandidates.add(cred.toString()); 270 } 271 } else { 272 for (Principal p : 273 new ArrayList<Principal>(s.getPrincipals())) { 274 rsCandidates.add(p.getName()); 275 } 276 } 277 logger.finer(Integer.toString(rsCandidates.size()) 278 + " candidate " + (roleSchemaViaCredential 279 ? "Credentials" : "Principals")); 280 for (String candid : rsCandidates) { 281 m = roleSchemaValuePattern.matcher(candid); 282 if (m.matches()) { 283 logger.finer(" +" + ++i + ": " 284 + ((m.groupCount() > 0) ? m.group(1) : candid)); 285 rsList.add((m.groupCount() > 0) ? m.group(1) : candid); 286 } else { 287 logger.finer(" -" + ++i + ": " + candid); 288 } 289 } 290 return rsList.toArray(new String[0]); 291 } finally { 292 lc.logout(); 293 } 294 } catch (LoginException le) { 295 logger.severe("System JaasAuthBean failure", le); 296 throw new RuntimeException(le); // JAAS System failure 297 } catch (RuntimeException re) { 298 logger.severe("System JaasAuthBean failure", re); 299 throw re; 300 } 301 } 302 } 303