001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.activemq.shiro.authc;
018
019import org.apache.activemq.shiro.ConnectionReference;
020import org.apache.activemq.shiro.subject.SubjectConnectionReference;
021import org.apache.shiro.subject.PrincipalCollection;
022import org.apache.shiro.subject.SimplePrincipalCollection;
023import org.apache.shiro.subject.Subject;
024
025import java.util.Collection;
026
027/**
028 * @since 5.10.0
029 */
030public class DefaultAuthenticationPolicy implements AuthenticationPolicy {
031
032    private boolean vmConnectionAuthenticationRequired = false;
033    private String systemAccountUsername = "system";
034    private String systemAccountRealmName = "iniRealm";
035
036    private boolean anonymousAccessAllowed = false;
037    private String anonymousAccountUsername = "anonymous";
038    private String anonymousAccountRealmName = "iniRealm";
039
040    public boolean isVmConnectionAuthenticationRequired() {
041        return vmConnectionAuthenticationRequired;
042    }
043
044    public void setVmConnectionAuthenticationRequired(boolean vmConnectionAuthenticationRequired) {
045        this.vmConnectionAuthenticationRequired = vmConnectionAuthenticationRequired;
046    }
047
048    public String getSystemAccountUsername() {
049        return systemAccountUsername;
050    }
051
052    public void setSystemAccountUsername(String systemAccountUsername) {
053        this.systemAccountUsername = systemAccountUsername;
054    }
055
056    public String getSystemAccountRealmName() {
057        return systemAccountRealmName;
058    }
059
060    public void setSystemAccountRealmName(String systemAccountRealmName) {
061        this.systemAccountRealmName = systemAccountRealmName;
062    }
063
064    public boolean isAnonymousAccessAllowed() {
065        return anonymousAccessAllowed;
066    }
067
068    public void setAnonymousAccessAllowed(boolean anonymousAccessAllowed) {
069        this.anonymousAccessAllowed = anonymousAccessAllowed;
070    }
071
072    public String getAnonymousAccountUsername() {
073        return anonymousAccountUsername;
074    }
075
076    public void setAnonymousAccountUsername(String anonymousAccountUsername) {
077        this.anonymousAccountUsername = anonymousAccountUsername;
078    }
079
080    public String getAnonymousAccountRealmName() {
081        return anonymousAccountRealmName;
082    }
083
084    public void setAnonymousAccountRealmName(String anonymousAccountRealmName) {
085        this.anonymousAccountRealmName = anonymousAccountRealmName;
086    }
087
088    /**
089     * Returns {@code true} if the client connection has supplied credentials to authenticate itself, {@code false}
090     * otherwise.
091     *
092     * @param conn the client's connection context
093     * @return {@code true} if the client connection has supplied credentials to authenticate itself, {@code false}
094     *         otherwise.
095     */
096    protected boolean credentialsAvailable(ConnectionReference conn) {
097        return conn.getConnectionInfo().getUserName() != null || conn.getConnectionInfo().getPassword() != null;
098    }
099
100    @Override
101    public boolean isAuthenticationRequired(SubjectConnectionReference conn) {
102        Subject subject = conn.getSubject();
103
104        if (subject.isAuthenticated()) {
105            //already authenticated:
106            return false;
107        }
108        //subject is not authenticated.  Authentication is required by default for all accounts other than
109        //the anonymous user (if enabled) or the vm account (if enabled)
110        if (isAnonymousAccessAllowed()) {
111            if (isAnonymousAccount(subject)) {
112                return false;
113            }
114        }
115
116        if (!isVmConnectionAuthenticationRequired()) {
117            if (isSystemAccount(subject)) {
118                return false;
119            }
120        }
121
122        return true;
123    }
124
125    protected boolean isAnonymousAccount(Subject subject) {
126        PrincipalCollection pc = subject.getPrincipals();
127        return pc != null && matches(pc, anonymousAccountUsername, anonymousAccountRealmName);
128    }
129
130    protected boolean isSystemAccount(Subject subject) {
131        PrincipalCollection pc = subject.getPrincipals();
132        return pc != null && matches(pc, systemAccountUsername, systemAccountRealmName);
133    }
134
135    protected boolean matches(PrincipalCollection principals, String username, String realmName) {
136        Collection realmPrincipals = principals.fromRealm(realmName);
137        if (realmPrincipals != null && !realmPrincipals.isEmpty()) {
138            if (realmPrincipals.iterator().next().equals(username)) {
139                return true;
140            }
141        }
142        return false;
143    }
144
145    protected boolean isSystemConnection(ConnectionReference conn) {
146        String remoteAddress = conn.getConnectionContext().getConnection().getRemoteAddress();
147        return remoteAddress.startsWith("vm:");
148    }
149
150    @Override
151    public void customizeSubject(Subject.Builder subjectBuilder, ConnectionReference conn) {
152        // We only need to specify a custom identity or authentication state if a normal authentication will not occur.
153        // If the client supplied connection credentials, the AuthenticationFilter will perform a normal authentication,
154        // so we should exit immediately:
155        if (credentialsAvailable(conn)) {
156            return;
157        }
158
159        //The connection cannot be authenticated, potentially implying a system or anonymous connection.  Check if so:
160        if (isAssumeIdentity(conn)) {
161            PrincipalCollection assumedIdentity = createAssumedIdentity(conn);
162            subjectBuilder.principals(assumedIdentity);
163        }
164    }
165
166    /**
167     * Returns {@code true} if an unauthenticated connection should still assume a specific identity, {@code false}
168     * otherwise.  This method will <em>only</em> be called if there are no connection
169     * {@link #credentialsAvailable(ConnectionReference) credentialsAvailable}.
170     * If a client supplies connection credentials, they will always be used to authenticate the client with that
171     * identity.
172     * <p/>
173     * If {@code true} is returned, the assumed identity will be returned by
174     * {@link #createAssumedIdentity(ConnectionReference) createAssumedIdentity}.
175     * <h3>Warning</h3>
176     * This method exists primarily to support the system and anonymous accounts - it is probably unsafe to return
177     * {@code true} in most other scenarios.
178     *
179     * @param conn a reference to the client's connection
180     * @return {@code true} if an unauthenticated connection should still assume a specific identity, {@code false}
181     *         otherwise.
182     */
183    protected boolean isAssumeIdentity(ConnectionReference conn) {
184        return isAnonymousAccessAllowed() ||
185                (isSystemConnection(conn) && !isVmConnectionAuthenticationRequired());
186    }
187
188    /**
189     * Returns a Shiro {@code PrincipalCollection} representing the identity to assume (without true authentication) for
190     * the specified Connection.
191     * <p/>
192     * This method is <em>only</em> called if {@link #isAssumeIdentity(ConnectionReference)} is {@code true}.
193     *
194     * @param conn a reference to the client's connection
195     * @return a Shiro {@code PrincipalCollection} representing the identity to assume (without true authentication) for
196     *         the specified Connection.
197     */
198    protected PrincipalCollection createAssumedIdentity(ConnectionReference conn) {
199
200        //anonymous by default:
201        String username = anonymousAccountUsername;
202        String realmName = anonymousAccountRealmName;
203
204        //vm connections are special and should assume the system account:
205        if (isSystemConnection(conn)) {
206            username = systemAccountUsername;
207            realmName = systemAccountRealmName;
208        }
209
210        return new SimplePrincipalCollection(username, realmName);
211    }
212}