validate(Authentication,String,RegisteredService)   F
last analyzed

Complexity

Conditions 17

Size

Total Lines 100

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 100
c 0
b 0
f 0
cc 17
rs 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like org.apereo.cas.authentication.DefaultAuthenticationContextValidator.validate(Authentication,String,RegisteredService) often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
package org.apereo.cas.authentication;
2
3
4
import org.apache.commons.lang3.StringUtils;
5
import org.apache.commons.lang3.tuple.Pair;
6
import org.apereo.cas.services.MultifactorAuthenticationProvider;
7
import org.apereo.cas.services.RegisteredService;
8
import org.apereo.cas.services.RegisteredServiceMultifactorPolicy;
9
import org.apereo.cas.util.CollectionUtils;
10
import org.slf4j.Logger;
11
import org.slf4j.LoggerFactory;
12
import org.springframework.beans.factory.annotation.Autowired;
13
import org.springframework.context.ConfigurableApplicationContext;
14
import org.springframework.core.OrderComparator;
15
16
17
import java.util.Arrays;
18
import java.util.Collection;
19
import java.util.Map;
20
import java.util.Optional;
21
22
/**
23
 * The {@link DefaultAuthenticationContextValidator} is responsible for evaluating an authentication
24
 * object to see whether it satisfied a requested authentication context.
25
 *
26
 * @author Misagh Moayyed
27
 * @since 4.3
28
 */
29
public class DefaultAuthenticationContextValidator implements AuthenticationContextValidator {
30
31
    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultAuthenticationContextValidator.class);
32
33
    private final String authenticationContextAttribute;
34
    private final String globalFailureMode;
35
    private final String mfaTrustedAuthnAttributeName;
36
37
    @Autowired
38
    private ConfigurableApplicationContext applicationContext;
39
40
    public DefaultAuthenticationContextValidator(final String contextAttribute, final String failureMode, final String authnAttributeName) {
41
        this.authenticationContextAttribute = contextAttribute;
42
        this.globalFailureMode = failureMode;
43
        this.mfaTrustedAuthnAttributeName = authnAttributeName;
44
    }
45
46
    public String getAuthenticationContextAttribute() {
47
        return this.authenticationContextAttribute;
48
    }
49
50
    /**
51
     * {@inheritDoc}
52
     * If the authentication event is established as part trusted/device browser
53
     * such that MFA was skipped, allow for validation to execute successfully.
54
     * If authentication event did bypass MFA, let for allow for validation to execute successfully.
55
     *
56
     * @param authentication   the authentication
57
     * @param requestedContext the requested context
58
     * @param service          the service
59
     * @return true if the context can be successfully validated.
60
     */
61
    @Override
62
    public Pair<Boolean, Optional<MultifactorAuthenticationProvider>> validate(final Authentication authentication,
63
                                                                               final String requestedContext,
64
                                                                               final RegisteredService service) {
65
        final Map<String, Object> attrs = authentication.getAttributes();
66
        final Object ctxAttr = attrs.get(this.authenticationContextAttribute);
67
        final Collection<Object> contexts = CollectionUtils.toCollection(ctxAttr);
68
        LOGGER.debug("Attempting to match requested authentication context [{}] against [{}]", requestedContext, contexts);
69
70
        final Map<String, MultifactorAuthenticationProvider> providerMap = 
71
                MultifactorAuthenticationUtils.getAvailableMultifactorAuthenticationProviders(this.applicationContext);
72
        if (providerMap == null) {
73
            LOGGER.debug("No multifactor authentication providers are configured");
74
            return Pair.of(Boolean.FALSE, Optional.empty());
75
        }
76
        final Optional<MultifactorAuthenticationProvider> requestedProvider = locateRequestedProvider(providerMap.values(), requestedContext);
77
78
        if (!requestedProvider.isPresent()) {
79
            LOGGER.debug("Requested authentication provider cannot be recognized.");
80
            return Pair.of(Boolean.FALSE, Optional.empty());
81
        }
82
83
        if (contexts.stream().filter(ctx -> ctx.toString().equals(requestedContext)).count() > 0) {
84
            LOGGER.debug("Requested authentication context [{}] is satisfied", requestedContext);
85
            return Pair.of(Boolean.TRUE, requestedProvider);
86
        }
87
88
89
        if (StringUtils.isNotBlank(this.mfaTrustedAuthnAttributeName)
90
                && attrs.containsKey(this.mfaTrustedAuthnAttributeName)) {
91
            LOGGER.debug("Requested authentication context [{}] is satisfied since device is already trusted", requestedContext);
92
            return Pair.of(Boolean.TRUE, requestedProvider);
93
        }
94
95
        if (attrs.containsKey(MultifactorAuthenticationProviderBypass.AUTHENTICATION_ATTRIBUTE_BYPASS_MFA)
96
                && attrs.containsKey(MultifactorAuthenticationProviderBypass.AUTHENTICATION_ATTRIBUTE_BYPASS_MFA_PROVIDER)) {
97
98
            final boolean isBypass = Boolean.class.cast(attrs.get(MultifactorAuthenticationProviderBypass.AUTHENTICATION_ATTRIBUTE_BYPASS_MFA));
99
            final String bypassedId = attrs.get(MultifactorAuthenticationProviderBypass.AUTHENTICATION_ATTRIBUTE_BYPASS_MFA_PROVIDER).toString();
100
101
            LOGGER.debug("Found multifactor authentication bypass attributes for provider [{}]", bypassedId);
102
103
            if (isBypass && StringUtils.equals(bypassedId, requestedContext)) {
104
                LOGGER.debug("Requested authentication context [{}] is satisfied given mfa was bypassed for the authentication attempt", 
105
                        requestedContext);
106
                return Pair.of(Boolean.TRUE, requestedProvider);
107
            }
108
109
            LOGGER.debug("Either multifactor authentication was not bypassed or the requested context [{}] does not match the bypassed provider [{}]",
110
                    requestedProvider, bypassedId);
111
        }
112
113
        final Collection<MultifactorAuthenticationProvider> satisfiedProviders =
114
                getSatisfiedAuthenticationProviders(authentication, providerMap.values());
115
116
        if (satisfiedProviders == null) {
117
            LOGGER.warn("No satisfied multifactor authentication providers are recorded in the current authentication context.");
118
            return Pair.of(Boolean.FALSE, requestedProvider);
119
        }
120
121
        if (!satisfiedProviders.isEmpty()) {
122
            final MultifactorAuthenticationProvider[] providers = satisfiedProviders.toArray(new MultifactorAuthenticationProvider[]{});
123
            OrderComparator.sortIfNecessary(providers);
124
            final Optional<MultifactorAuthenticationProvider> result = Arrays.stream(providers)
125
                    .filter(provider -> {
126
                        final MultifactorAuthenticationProvider p = requestedProvider.get();
127
                        return provider.equals(p) || provider.getOrder() >= p.getOrder();
128
                    })
129
                    .findFirst();
130
131
            if (result.isPresent()) {
132
                LOGGER.debug("Current provider [{}] already satisfies the authentication requirements of [{}]; proceed with flow normally.",
133
                        result.get(), requestedProvider);
134
                return Pair.of(Boolean.TRUE, requestedProvider);
135
            }
136
        }
137
138
        LOGGER.debug("No multifactor providers could be located to satisfy the requested context for [{}]", requestedProvider);
139
140
        final RegisteredServiceMultifactorPolicy.FailureModes mode = getMultifactorFailureModeForService(service);
141
        if (mode == RegisteredServiceMultifactorPolicy.FailureModes.PHANTOM) {
142
            if (!requestedProvider.get().isAvailable(service)) {
143
                LOGGER.debug("Service [{}] is configured to use a [{}] failure mode for multifactor authentication policy. "
144
                                + "Since provider [{}] is unavailable at the moment, CAS will knowingly allow [{}] as a satisfied criteria "
145
                                + "of the present authentication context", service.getServiceId(),
146
                        mode, requestedProvider, requestedContext);
147
                return Pair.of(Boolean.TRUE, requestedProvider);
148
            }
149
        }
150
        if (mode == RegisteredServiceMultifactorPolicy.FailureModes.OPEN) {
151
            if (!requestedProvider.get().isAvailable(service)) {
152
                LOGGER.debug("Service [{}] is configured to use a [{}] failure mode for multifactor authentication policy and "
153
                                + "since provider [{}] is unavailable at the moment, CAS will consider the authentication satisfied "
154
                                + "without the presence of [{}]", service.getServiceId(),
155
                        mode, requestedProvider, requestedContext);
156
                return Pair.of(Boolean.TRUE, satisfiedProviders.stream().findFirst());
157
            }
158
        }
159
160
        return Pair.of(Boolean.FALSE, requestedProvider);
161
    }
162
    
163
164
    private Collection<MultifactorAuthenticationProvider> getSatisfiedAuthenticationProviders(final Authentication authentication,
165
            final Collection<MultifactorAuthenticationProvider> providers) {
166
        final Collection<Object> contexts = CollectionUtils.toCollection(
167
                authentication.getAttributes().get(this.authenticationContextAttribute));
168
169
        if (contexts == null || contexts.isEmpty()) {
170
            LOGGER.debug("No authentication context could be determined based on authentication attribute [{}]",
171
                    this.authenticationContextAttribute);
172
            return null;
173
        }
174
175
        contexts.stream().forEach(context ->
176
                providers.removeIf(provider -> !provider.getId().equals(context))
177
        );
178
179
        LOGGER.debug("Found [{}] providers that may satisfy the context", providers.size());
180
        return providers;
181
    }
182
183
184
    private static Optional<MultifactorAuthenticationProvider> locateRequestedProvider(final Collection<MultifactorAuthenticationProvider> providersArray,
185
                                                                                       final String requestedProvider) {
186
        return providersArray.stream()
187
                .filter(provider -> provider.getId().equals(requestedProvider))
188
                .findFirst();
189
    }
190
191
    private RegisteredServiceMultifactorPolicy.FailureModes getMultifactorFailureModeForService(final RegisteredService service) {
192
        final RegisteredServiceMultifactorPolicy policy = service.getMultifactorPolicy();
193
        if (policy == null || policy.getFailureMode() == null) {
194
            return RegisteredServiceMultifactorPolicy.FailureModes.valueOf(this.globalFailureMode);
195
        }
196
        return policy.getFailureMode();
197
    }
198
}
199