Test Failed
Push — master ( cfc629...7c8fa2 )
by Misagh
14:48
created

getDuoUserAccount(String)   B

Complexity

Conditions 6

Size

Total Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
c 0
b 0
f 0
dl 0
loc 32
rs 7.5384
1
package org.apereo.cas.adaptors.duo.authn;
2
3
import com.duosecurity.client.Http;
4
import com.fasterxml.jackson.databind.JsonNode;
5
import com.fasterxml.jackson.databind.ObjectMapper;
6
import org.apache.commons.lang3.builder.EqualsBuilder;
7
import org.apache.commons.lang3.builder.HashCodeBuilder;
8
import org.apereo.cas.adaptors.duo.DuoUserAccount;
9
import org.apereo.cas.adaptors.duo.DuoUserAccountAuthStatus;
10
import org.apereo.cas.configuration.model.support.mfa.DuoSecurityMultifactorProperties;
11
import org.apereo.cas.util.http.HttpClient;
12
import org.apereo.cas.util.http.HttpMessage;
13
import org.slf4j.Logger;
14
import org.slf4j.LoggerFactory;
15
import org.springframework.http.HttpMethod;
16
17
import java.net.URL;
18
import java.net.URLDecoder;
19
import java.nio.charset.StandardCharsets;
20
21
/**
22
 * This is {@link BaseDuoSecurityAuthenticationService}.
23
 *
24
 * @author Misagh Moayyed
25
 * @since 5.1.0
26
 */
27
public abstract class BaseDuoSecurityAuthenticationService implements DuoSecurityAuthenticationService {
28
    private static final long serialVersionUID = -8044100706027708789L;
29
30
    private static final int AUTH_API_VERSION = 2;
31
    private static final String RESULT_KEY_RESPONSE = "response";
32
    private static final String RESULT_KEY_STAT = "stat";
33
    private static final String RESULT_KEY_RESULT = "result";
34
    private static final String RESULT_KEY_ENROLL_PORTAL_URL = "enroll_portal_url";
35
    private static final String RESULT_KEY_STATUS_MESSAGE = "status_msg";
36
37
    private static final ObjectMapper MAPPER = new ObjectMapper().findAndRegisterModules();
38
39
    private static final Logger LOGGER = LoggerFactory.getLogger(BaseDuoSecurityAuthenticationService.class);
40
41
    /**
42
     * Duo Properties.
43
     */
44
    protected final DuoSecurityMultifactorProperties duoProperties;
45
46
    private final transient HttpClient httpClient;
47
48
    /**
49
     * Creates the duo authentication service.
50
     *
51
     * @param duoProperties the duo properties
52
     * @param httpClient    the http client
53
     */
54
    public BaseDuoSecurityAuthenticationService(final DuoSecurityMultifactorProperties duoProperties, final HttpClient httpClient) {
55
        this.duoProperties = duoProperties;
56
        this.httpClient = httpClient;
57
    }
58
59
    @Override
60
    public boolean ping() {
61
        try {
62
            final String url = buildUrlHttpScheme(getApiHost().concat("/rest/v1/ping"));
63
            LOGGER.debug("Contacting Duo @ [{}]", url);
64
65
            final HttpMessage msg = this.httpClient.sendMessageToEndPoint(new URL(url));
66
            if (msg != null) {
67
                final String response = URLDecoder.decode(msg.getMessage(), StandardCharsets.UTF_8.name());
68
                LOGGER.debug("Received Duo ping response [{}]", response);
69
70
                final JsonNode result = MAPPER.readTree(response);
71
                if (result.has(RESULT_KEY_RESPONSE) && result.has(RESULT_KEY_STAT)
72
                        && result.get(RESULT_KEY_RESPONSE).asText().equalsIgnoreCase("pong")
73
                        && result.get(RESULT_KEY_STAT).asText().equalsIgnoreCase("OK")) {
74
                    return true;
75
                }
76
                LOGGER.warn("Could not reach/ping Duo. Response returned is [{}]", result);
77
            }
78
        } catch (final Exception e) {
79
            LOGGER.warn("Pinging Duo has failed with error: [{}]", e.getMessage(), e);
80
        }
81
        return false;
82
    }
83
84
    @Override
85
    public String getApiHost() {
86
        return duoProperties.getDuoApiHost();
87
    }
88
89
    @Override
90
    public boolean equals(final Object obj) {
91
        if (obj == null) {
92
            return false;
93
        }
94
        if (obj == this) {
95
            return true;
96
        }
97
        if (obj.getClass() != getClass()) {
98
            return false;
99
        }
100
        final BaseDuoSecurityAuthenticationService rhs = (BaseDuoSecurityAuthenticationService) obj;
101
        return new EqualsBuilder()
102
                .append(this.duoProperties.getDuoApiHost(), rhs.duoProperties.getDuoApiHost())
103
                .append(this.duoProperties.getDuoApplicationKey(), rhs.duoProperties.getDuoApplicationKey())
104
                .append(this.duoProperties.getDuoIntegrationKey(), rhs.duoProperties.getDuoIntegrationKey())
105
                .append(this.duoProperties.getDuoSecretKey(), rhs.duoProperties.getDuoSecretKey())
106
                .isEquals();
107
    }
108
109
    @Override
110
    public int hashCode() {
111
        return new HashCodeBuilder()
112
                .append(this.duoProperties.getDuoApiHost())
113
                .append(this.duoProperties.getDuoApplicationKey())
114
                .append(this.duoProperties.getDuoIntegrationKey())
115
                .append(this.duoProperties.getDuoSecretKey())
116
                .toHashCode();
117
    }
118
119
    @Override
120
    public DuoUserAccount getDuoUserAccount(final String username) {
121
        final DuoUserAccount account = new DuoUserAccount(username);
122
        account.setStatus(DuoUserAccountAuthStatus.AUTH);
123
124
        try {
125
            final Http userRequest = buildHttpPostUserPreAuthRequest(username);
126
            signHttpUserPreAuthRequest(userRequest);
127
            LOGGER.debug("Contacting Duo to inquire about username [{}]", username);
128
            final String userResponse = userRequest.executeHttpRequest().body().string();
129
            final String jsonResponse = URLDecoder.decode(userResponse, StandardCharsets.UTF_8.name());
130
            LOGGER.debug("Received Duo admin response [{}]", jsonResponse);
131
132
            final JsonNode result = MAPPER.readTree(jsonResponse);
133
            if (result.has(RESULT_KEY_RESPONSE) && result.has(RESULT_KEY_STAT)
134
                    && result.get(RESULT_KEY_STAT).asText().equalsIgnoreCase("OK")) {
135
136
                final JsonNode response = result.get(RESULT_KEY_RESPONSE);
137
                final String authResult = response.get(RESULT_KEY_RESULT).asText().toUpperCase();
138
139
                final DuoUserAccountAuthStatus status = DuoUserAccountAuthStatus.valueOf(authResult);
140
                account.setStatus(status);
141
                account.setMessage(response.get(RESULT_KEY_STATUS_MESSAGE).asText());
142
                if (status == DuoUserAccountAuthStatus.ENROLL) {
143
                    final String enrollUrl = response.get(RESULT_KEY_ENROLL_PORTAL_URL).asText();
144
                    account.setEnrollPortalUrl(enrollUrl);
145
                }
146
            }
147
        } catch (final Exception e) {
148
            LOGGER.warn("Reaching Duo has failed with error: [{}]", e.getMessage(), e);
149
        }
150
        return account;
151
    }
152
153
    private static String buildUrlHttpScheme(final String url) {
154
        if (!url.startsWith("http")) {
155
            return "https://" + url;
156
        }
157
        return url;
158
    }
159
160
    /**
161
     * Build http post auth request http.
162
     *
163
     * @return the http
164
     */
165
    protected Http buildHttpPostAuthRequest() {
166
        return new Http(HttpMethod.POST.name(),
167
                duoProperties.getDuoApiHost(),
168
                String.format("/auth/v%s/auth", AUTH_API_VERSION));
169
    }
170
171
    /**
172
     * Build http post get user auth request.
173
     *
174
     * @param username the username
175
     * @return the http
176
     */
177
    protected Http buildHttpPostUserPreAuthRequest(final String username) {
178
        final Http usersRequest = new Http(HttpMethod.POST.name(),
179
                duoProperties.getDuoApiHost(),
180
                String.format("/auth/v%s/preauth", AUTH_API_VERSION));
181
        usersRequest.addParam("username", username);
182
        return usersRequest;
183
    }
184
185
    /**
186
     * Sign http request.
187
     *
188
     * @param request the request
189
     * @param id      the id
190
     * @return the http
191
     */
192
    protected Http signHttpAuthRequest(final Http request, final String id) {
193
        try {
194
            request.addParam("username", id);
195
            request.addParam("factor", "auto");
196
            request.addParam("device", "auto");
197
            request.signRequest(
198
                    duoProperties.getDuoIntegrationKey(),
199
                    duoProperties.getDuoSecretKey());
200
            return request;
201
        } catch (final Exception e) {
202
            throw new RuntimeException(e.getMessage(), e);
203
        }
204
    }
205
206
    /**
207
     * Sign http users request http.
208
     *
209
     * @param request the request
210
     * @return the http
211
     */
212
    protected Http signHttpUserPreAuthRequest(final Http request) {
213
        try {
214
            request.signRequest(
215
                    duoProperties.getDuoIntegrationKey(),
216
                    duoProperties.getDuoSecretKey());
217
            return request;
218
        } catch (final Exception e) {
219
            throw new RuntimeException(e.getMessage(), e);
220
        }
221
    }
222
}
223