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}