aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/traccar/database
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/traccar/database')
-rw-r--r--src/main/java/org/traccar/database/OpenIdProvider.java183
1 files changed, 183 insertions, 0 deletions
diff --git a/src/main/java/org/traccar/database/OpenIdProvider.java b/src/main/java/org/traccar/database/OpenIdProvider.java
new file mode 100644
index 000000000..fc1409d14
--- /dev/null
+++ b/src/main/java/org/traccar/database/OpenIdProvider.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2023 Daniel Raper (me@danr.uk)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.traccar.database;
+
+import org.traccar.config.Config;
+import org.traccar.config.Keys;
+import org.traccar.api.resource.SessionResource;
+import org.traccar.api.security.LoginService;
+import org.traccar.model.User;
+import org.traccar.storage.StorageException;
+import org.traccar.helper.LogAction;
+import org.traccar.helper.ServletHelper;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.io.IOException;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import com.google.inject.Inject;
+
+import com.nimbusds.oauth2.sdk.http.HTTPResponse;
+import com.nimbusds.oauth2.sdk.AuthorizationCode;
+import com.nimbusds.oauth2.sdk.ResponseType;
+import com.nimbusds.oauth2.sdk.Scope;
+import com.nimbusds.oauth2.sdk.AuthorizationGrant;
+import com.nimbusds.oauth2.sdk.TokenRequest;
+import com.nimbusds.oauth2.sdk.TokenResponse;
+import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
+import com.nimbusds.oauth2.sdk.AuthorizationErrorResponse;
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.AuthorizationResponse;
+import com.nimbusds.oauth2.sdk.auth.Secret;
+import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
+import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
+import com.nimbusds.oauth2.sdk.id.State;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+import com.nimbusds.openid.connect.sdk.Nonce;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponseParser;
+import com.nimbusds.openid.connect.sdk.UserInfoResponse;
+import com.nimbusds.openid.connect.sdk.UserInfoRequest;
+import com.nimbusds.openid.connect.sdk.AuthenticationRequest;
+
+import com.nimbusds.openid.connect.sdk.claims.UserInfo;
+
+public class OpenIdProvider {
+ public final Boolean force;
+ private final ClientID clientId;
+ private final Secret clientSecret;
+ private URI callbackUrl;
+ private URI authUrl;
+ private URI tokenUrl;
+ private URI userInfoUrl;
+ private URI baseUrl;
+ private final String adminGroup;
+
+ private LoginService loginService;
+
+ @Inject
+ public OpenIdProvider(Config config, LoginService loginService) {
+ force = config.getBoolean(Keys.OPENID_FORCE);
+ clientId = new ClientID(config.getString(Keys.OPENID_CLIENTID));
+ clientSecret = new Secret(config.getString(Keys.OPENID_CLIENTSECRET));
+
+ this.loginService = loginService;
+
+ try {
+ callbackUrl = new URI(config.getString(Keys.WEB_URL, "") + "/api/session/openid/callback");
+ authUrl = new URI(config.getString(Keys.OPENID_AUTHURL, ""));
+ tokenUrl = new URI(config.getString(Keys.OPENID_TOKENURL, ""));
+ userInfoUrl = new URI(config.getString(Keys.OPENID_USERINFOURL, ""));
+ baseUrl = new URI(config.getString(Keys.WEB_URL, ""));
+ } catch (URISyntaxException e) {
+ }
+
+ adminGroup = config.getString(Keys.OPENID_ADMINGROUP);
+ }
+
+ public URI createAuthUri() {
+ AuthenticationRequest request = new AuthenticationRequest.Builder(
+ new ResponseType("code"),
+ new Scope("openid", "profile", "email", "groups"),
+ clientId,
+ callbackUrl)
+ .endpointURI(authUrl)
+ .state(new State())
+ .nonce(new Nonce())
+ .build();
+
+ return request.toURI();
+ }
+
+ private OIDCTokenResponse getToken(AuthorizationCode code) {
+ // Credentials to authenticate us to the token endpoint
+ ClientAuthentication clientAuth = new ClientSecretBasic(clientId, clientSecret);
+ AuthorizationGrant codeGrant = new AuthorizationCodeGrant(code, callbackUrl);
+
+ TokenRequest request = new TokenRequest(tokenUrl, clientAuth, codeGrant);
+ TokenResponse tokenResponse;
+
+ try {
+ HTTPResponse tokenReq = request.toHTTPRequest().send();
+ tokenResponse = OIDCTokenResponseParser.parse(tokenReq);
+ if (!tokenResponse.indicatesSuccess()) {
+ return null;
+ }
+
+ return (OIDCTokenResponse) tokenResponse.toSuccessResponse();
+ } catch (IOException e) {
+ return null;
+ } catch (ParseException e) {
+ return null;
+ }
+ }
+
+ private UserInfo getUserInfo(BearerAccessToken token) throws IOException, ParseException {
+ UserInfoResponse userInfoResponse;
+
+ HTTPResponse httpResponse = new UserInfoRequest(userInfoUrl, token)
+ .toHTTPRequest()
+ .send();
+
+ userInfoResponse = UserInfoResponse.parse(httpResponse);
+
+ if (!userInfoResponse.indicatesSuccess()) {
+ // User info request failed - usually from expiring
+ return null;
+ }
+
+ return userInfoResponse.toSuccessResponse().getUserInfo();
+ }
+
+ public Response handleCallback(URI requestUri, HttpServletRequest request) throws StorageException, ParseException, IOException, WebApplicationException {
+ AuthorizationResponse response = AuthorizationResponse.parse(requestUri);
+
+ if (!response.indicatesSuccess()) {
+ AuthorizationErrorResponse error = response.toErrorResponse();
+ throw new WebApplicationException(Response.status(403).entity(error.getErrorObject().getDescription()).build());
+ }
+
+ AuthorizationCode authCode = response.toSuccessResponse().getAuthorizationCode();
+
+ if (authCode == null) {
+ return Response.status(403).entity( "Invalid OpenID Connect callback.").build();
+ }
+
+ OIDCTokenResponse tokens = getToken(authCode);
+
+ if (tokens == null) {
+ return Response.status(403).entity("Unable to authenticate with the OpenID Connect provider. Please try again.").build();
+ }
+
+ BearerAccessToken bearerToken = tokens.getOIDCTokens().getBearerAccessToken();
+
+ UserInfo userInfo = getUserInfo(bearerToken);
+
+ if (userInfo == null) {
+ return Response.status(500).entity("Failed to access OpenID Connect user info endpoint. Please contact your administrator.").build();
+ }
+
+ User user = loginService.login(userInfo.getEmailAddress(), userInfo.getName(), userInfo.getStringListClaim("groups").contains(adminGroup));
+
+ request.getSession().setAttribute(SessionResource.USER_ID_KEY, user.getId());
+ LogAction.login(user.getId(), ServletHelper.retrieveRemoteAddress(request));
+ return Response.seeOther(
+ baseUrl).build();
+ }
+}