001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 020package org.apache.isis.core.runtime.authentication.standard; 021 022import static org.apache.isis.core.commons.ensure.Ensure.ensureThatArg; 023import static org.hamcrest.CoreMatchers.is; 024import static org.hamcrest.CoreMatchers.notNullValue; 025 026import java.util.Collection; 027import java.util.Collections; 028import java.util.List; 029import java.util.Map; 030 031import com.google.common.collect.Collections2; 032import com.google.common.collect.Lists; 033import com.google.common.collect.Maps; 034 035import org.apache.isis.core.commons.authentication.AuthenticationSession; 036import org.apache.isis.core.commons.config.IsisConfiguration; 037import org.apache.isis.core.commons.debug.DebugBuilder; 038import org.apache.isis.core.commons.debug.DebuggableWithTitle; 039import org.apache.isis.core.commons.exceptions.IsisException; 040import org.apache.isis.core.commons.util.ToString; 041import org.apache.isis.core.runtime.authentication.AuthenticationManager; 042import org.apache.isis.core.runtime.authentication.AuthenticationRequest; 043import org.apache.isis.core.runtime.authentication.RegistrationDetails; 044 045public class AuthenticationManagerStandard implements AuthenticationManager, DebuggableWithTitle { 046 047 private final Map<String, String> userByValidationCode = Maps.newHashMap(); 048 049 /** 050 * Not final because may be set {@link #setAuthenticators(List) 051 * programmatically}. 052 */ 053 private List<Authenticator> authenticators = Lists.newArrayList(); 054 055 private RandomCodeGenerator randomCodeGenerator; 056 private final IsisConfiguration configuration; 057 058 // ////////////////////////////////////////////////////////// 059 // constructor 060 // ////////////////////////////////////////////////////////// 061 062 public AuthenticationManagerStandard(final IsisConfiguration configuration) { 063 this.configuration = configuration; 064 } 065 066 // ////////////////////////////////////////////////////////// 067 // init 068 // ////////////////////////////////////////////////////////// 069 070 /** 071 * Will default the {@link #setRandomCodeGenerator(RandomCodeGenerator) 072 * RandomCodeGenerator}, but {@link Authenticator}(s) must have been 073 * {@link #addAuthenticator(Authenticator) added} or 074 * {@link #setAuthenticators(List) injected}. 075 */ 076 @Override 077 public final void init() { 078 defaultRandomCodeGeneratorIfNecessary(); 079 addDefaultAuthenticators(); 080 if (authenticators.size() == 0) { 081 throw new IsisException("No authenticators specified"); 082 } 083 for (final Authenticator authenticator : authenticators) { 084 authenticator.init(); 085 } 086 } 087 088 private void defaultRandomCodeGeneratorIfNecessary() { 089 if (randomCodeGenerator == null) { 090 randomCodeGenerator = new RandomCodeGenerator10Chars(); 091 } 092 } 093 094 /** 095 * optional hook method 096 */ 097 protected void addDefaultAuthenticators() { 098 } 099 100 @Override 101 public void shutdown() { 102 for (final Authenticator authenticator : authenticators) { 103 authenticator.shutdown(); 104 } 105 } 106 107 // ////////////////////////////////////////////////////////// 108 // Session Management (including authenticate) 109 // ////////////////////////////////////////////////////////// 110 111 @Override 112 public synchronized final AuthenticationSession authenticate(final AuthenticationRequest request) { 113 if (request == null) { 114 return null; 115 } 116 117 final Collection<Authenticator> compatibleAuthenticators = Collections2.filter(authenticators, AuthenticatorFuncs.compatibleWith(request)); 118 if (compatibleAuthenticators.size() == 0) { 119 throw new NoAuthenticatorException("No authenticator available for processing " + request.getClass().getName()); 120 } 121 for (final Authenticator authenticator : compatibleAuthenticators) { 122 final AuthenticationSession authSession = authenticator.authenticate(request, getUnusedRandomCode()); 123 if (authSession != null) { 124 userByValidationCode.put(authSession.getValidationCode(), authSession.getUserName()); 125 return authSession; 126 } 127 } 128 return null; 129 } 130 131 private String getUnusedRandomCode() { 132 String code; 133 do { 134 code = randomCodeGenerator.generateRandomCode(); 135 } while (userByValidationCode.containsKey(code)); 136 137 return code; 138 } 139 140 @Override 141 public final boolean isSessionValid(final AuthenticationSession session) { 142 final String userName = userByValidationCode.get(session.getValidationCode()); 143 return session.hasUserNameOf(userName); 144 } 145 146 @Override 147 public void closeSession(final AuthenticationSession session) { 148 userByValidationCode.remove(session.getValidationCode()); 149 } 150 151 // ////////////////////////////////////////////////////////// 152 // Authenticators 153 // ////////////////////////////////////////////////////////// 154 155 /** 156 * Adds an {@link Authenticator}. 157 * 158 * <p> 159 * Use either this or alternatively {@link #setAuthenticators(List) inject} 160 * the full list of {@link Authenticator}s. 161 */ 162 public final void addAuthenticator(final Authenticator authenticator) { 163 authenticators.add(authenticator); 164 } 165 166 /** 167 * Adds an {@link Authenticator} to the start of the list (not API). 168 */ 169 protected void addAuthenticatorToStart(final Authenticator authenticator) { 170 authenticators.add(0, authenticator); 171 } 172 173 /** 174 * Provide direct injection. 175 * 176 * <p> 177 * Use either this or programmatically 178 * {@link #addAuthenticator(Authenticator)}. 179 */ 180 public void setAuthenticators(final List<Authenticator> authenticators) { 181 this.authenticators = authenticators; 182 } 183 184 public List<Authenticator> getAuthenticators() { 185 return Collections.unmodifiableList(authenticators); 186 } 187 188 // ////////////////////////////////////////////////////////// 189 // register 190 // ////////////////////////////////////////////////////////// 191 192 @Override 193 public boolean register(final RegistrationDetails registrationDetails) { 194 for (final Registrar registrar : getRegistrars()) { 195 if (registrar.canRegister(registrationDetails.getClass())) { 196 return registrar.register(registrationDetails); 197 } 198 } 199 return false; 200 } 201 202 @Override 203 public boolean supportsRegistration(final Class<? extends RegistrationDetails> registrationDetailsClass) { 204 for (final Registrar registrar : getRegistrars()) { 205 if (registrar.canRegister(registrationDetailsClass)) { 206 return true; 207 } 208 } 209 return false; 210 } 211 212 public List<Registrar> getRegistrars() { 213 return asAuthenticators(getAuthenticators()); 214 } 215 216 private static List<Registrar> asAuthenticators(final List<Authenticator> authenticators2) { 217 final List<Registrar> registrars = Lists.transform(authenticators2, Registrar.AS_REGISTRAR_ELSE_NULL); 218 return Lists.newArrayList(Collections2.filter(registrars, Registrar.NON_NULL)); 219 } 220 221 // ////////////////////////////////////////////////////////// 222 // RandomCodeGenerator 223 // ////////////////////////////////////////////////////////// 224 225 /** 226 * The {@link RandomCodeGenerator} in use. 227 */ 228 public RandomCodeGenerator getRandomCodeGenerator() { 229 return randomCodeGenerator; 230 } 231 232 /** 233 * For injection; will {@link #defaultRandomCodeGeneratorIfNecessary() 234 * default} otherwise. 235 */ 236 public void setRandomCodeGenerator(final RandomCodeGenerator randomCodeGenerator) { 237 ensureThatArg(randomCodeGenerator, is(notNullValue()), "randomCodeGenerator cannot be null"); 238 this.randomCodeGenerator = randomCodeGenerator; 239 } 240 241 // ////////////////////////////////////////////////////////// 242 // Debugging 243 // ////////////////////////////////////////////////////////// 244 245 @Override 246 public String debugTitle() { 247 return "Authentication Manager"; 248 } 249 250 @Override 251 public void debugData(final DebugBuilder debug) { 252 debug.appendTitle("Authenticators"); 253 for (final Authenticator authenticator : authenticators) { 254 debug.appendln(authenticator.toString()); 255 } 256 257 debug.appendTitle("Users"); 258 for (final String userName : userByValidationCode.values()) { 259 debug.appendln(userName); 260 } 261 } 262 263 @Override 264 public String toString() { 265 final ToString str = ToString.createAnonymous(this); 266 str.append("authenticators", authenticators.size()); 267 str.append("users", userByValidationCode.size()); 268 return str.toString(); 269 } 270 271 // ////////////////////////////////////////////////////////// 272 // Injected (constructor) 273 // ////////////////////////////////////////////////////////// 274 275 protected IsisConfiguration getConfiguration() { 276 return configuration; 277 } 278 279}