aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/traccar
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/traccar')
-rw-r--r--src/main/java/org/traccar/MainModule.java8
-rw-r--r--src/main/java/org/traccar/api/resource/ServerResource.java7
-rw-r--r--src/main/java/org/traccar/api/resource/SessionResource.java26
-rw-r--r--src/main/java/org/traccar/api/security/LoginService.java17
-rw-r--r--src/main/java/org/traccar/config/Keys.java68
-rw-r--r--src/main/java/org/traccar/database/OpenIdProvider.java (renamed from src/main/java/org/traccar/api/security/OpenIDProvider.java)130
-rw-r--r--src/main/java/org/traccar/model/Server.java28
7 files changed, 121 insertions, 163 deletions
diff --git a/src/main/java/org/traccar/MainModule.java b/src/main/java/org/traccar/MainModule.java
index 7def97657..7b06b4840 100644
--- a/src/main/java/org/traccar/MainModule.java
+++ b/src/main/java/org/traccar/MainModule.java
@@ -33,6 +33,7 @@ import org.traccar.broadcast.NullBroadcastService;
import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.database.LdapProvider;
+import org.traccar.database.OpenIdProvider;
import org.traccar.database.StatisticsManager;
import org.traccar.forward.EventForwarder;
import org.traccar.forward.EventForwarderJson;
@@ -88,7 +89,6 @@ import org.traccar.storage.MemoryStorage;
import org.traccar.storage.Storage;
import org.traccar.web.WebServer;
import org.traccar.api.security.LoginService;
-import org.traccar.api.security.OpenIDProvider;
import javax.annotation.Nullable;
import javax.inject.Singleton;
@@ -174,9 +174,9 @@ public class MainModule extends AbstractModule {
@Singleton
@Provides
- public static OpenIDProvider provideOpenIDProvider(Config config, LoginService loginService, Storage storage) {
- if (config.getBoolean(Keys.OIDC_ENABLE)) {
- return new OpenIDProvider(config, loginService, storage);
+ public static OpenIdProvider provideOpenIDProvider(Config config, LoginService loginService) {
+ if (config.hasKey(Keys.OPENID_CLIENTID)) {
+ return new OpenIdProvider(config, loginService);
}
return null;
}
diff --git a/src/main/java/org/traccar/api/resource/ServerResource.java b/src/main/java/org/traccar/api/resource/ServerResource.java
index 4b7ee9189..9b4d82a66 100644
--- a/src/main/java/org/traccar/api/resource/ServerResource.java
+++ b/src/main/java/org/traccar/api/resource/ServerResource.java
@@ -16,6 +16,7 @@
package org.traccar.api.resource;
import org.traccar.api.BaseResource;
+import org.traccar.database.OpenIdProvider;
import org.traccar.helper.model.UserUtil;
import org.traccar.mail.MailManager;
import org.traccar.geocoder.Geocoder;
@@ -57,6 +58,10 @@ public class ServerResource extends BaseResource {
@Inject
@Nullable
+ private OpenIdProvider openIdProvider;
+
+ @Inject
+ @Nullable
private Geocoder geocoder;
@PermitAll
@@ -65,6 +70,8 @@ public class ServerResource extends BaseResource {
Server server = storage.getObject(Server.class, new Request(new Columns.All()));
server.setEmailEnabled(mailManager.getEmailEnabled());
server.setGeocoderEnabled(geocoder != null);
+ server.setOpenIdEnabled(openIdProvider != null);
+ server.setOpenIdForce(openIdProvider != null && openIdProvider.force);
User user = permissionsService.getUser(getUserId());
if (user != null) {
if (user.getAdministrator()) {
diff --git a/src/main/java/org/traccar/api/resource/SessionResource.java b/src/main/java/org/traccar/api/resource/SessionResource.java
index 515d7374a..3de20b8c7 100644
--- a/src/main/java/org/traccar/api/resource/SessionResource.java
+++ b/src/main/java/org/traccar/api/resource/SessionResource.java
@@ -17,8 +17,8 @@ package org.traccar.api.resource;
import org.traccar.api.BaseResource;
import org.traccar.api.security.LoginService;
-import org.traccar.api.security.OpenIDProvider;
import org.traccar.api.signature.TokenManager;
+import org.traccar.database.OpenIdProvider;
import org.traccar.helper.DataConverter;
import org.traccar.helper.LogAction;
import org.traccar.helper.ServletHelper;
@@ -28,6 +28,7 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
+import com.nimbusds.oauth2.sdk.ParseException;
import javax.annotation.security.PermitAll;
import javax.inject.Inject;
import javax.servlet.http.Cookie;
@@ -65,7 +66,7 @@ public class SessionResource extends BaseResource {
private LoginService loginService;
@Inject
- private OpenIDProvider openIdProvider;
+ private OpenIdProvider openIdProvider;
@Inject
private TokenManager tokenManager;
@@ -169,28 +170,17 @@ public class SessionResource extends BaseResource {
@Path("openid/auth")
@GET
public Response openIdAuth() throws IOException {
- if (openIdProvider == null) {
- throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND).build());
- }
-
- return Response.seeOther(
- openIdProvider.createAuthRequest()
- ).build();
+ return Response.seeOther(openIdProvider.createAuthUri()).build();
}
@PermitAll
@Path("openid/callback")
@GET
- public Response requestToken() throws IOException, StorageException {
- if (openIdProvider == null) {
- throw new WebApplicationException(Response.status(Response.Status.NOT_FOUND).build());
- }
-
- // Get full request URI
- StringBuilder requestURL = new StringBuilder(request.getRequestURL().toString());
+ public Response requestToken() throws IOException, StorageException, ParseException {
+ StringBuilder requestUrl = new StringBuilder(request.getRequestURL().toString());
String queryString = request.getQueryString();
- String requestURI = requestURL.append('?').append(queryString).toString();
+ String requestUri = requestUrl.append('?').append(queryString).toString();
- return openIdProvider.handleCallback(URI.create(requestURI), request);
+ return openIdProvider.handleCallback(URI.create(requestUri), request);
}
}
diff --git a/src/main/java/org/traccar/api/security/LoginService.java b/src/main/java/org/traccar/api/security/LoginService.java
index 3d4f42b20..7b51667c8 100644
--- a/src/main/java/org/traccar/api/security/LoginService.java
+++ b/src/main/java/org/traccar/api/security/LoginService.java
@@ -89,17 +89,24 @@ public class LoginService {
return null;
}
- public User lookup(String email) throws StorageException {
+ public User login(String email, String name, Boolean administrator) throws StorageException {
User user = storage.getObject(User.class, new Request(
- new Columns.All(),
- new Condition.Equals("email", email)));
+ new Columns.All(),
+ new Condition.Equals("email", email)));
if (user != null) {
checkUserEnabled(user);
return user;
+ } else {
+ user = new User();
+ user.setName(name);
+ user.setEmail(email);
+ user.setFixedEmail(true);
+ user.setAdministrator(administrator);
+ user.setId(storage.addObject(user, new Request(new Columns.Exclude("id"))));
+ checkUserEnabled(user);
+ return user;
}
-
- return null;
}
private void checkUserEnabled(User user) throws SecurityException {
diff --git a/src/main/java/org/traccar/config/Keys.java b/src/main/java/org/traccar/config/Keys.java
index ace4c36af..a666667d4 100644
--- a/src/main/java/org/traccar/config/Keys.java
+++ b/src/main/java/org/traccar/config/Keys.java
@@ -610,61 +610,67 @@ public final class Keys {
"ldap.adminGroup",
List.of(KeyType.CONFIG));
-
/**
- * OIDC enable.
+ * Force OpenID Connect authentication. When enabled, the Traccar login page will be skipped
+ * and users are redirected to the OpenID Connect provider.
*/
- public static final ConfigKey<Boolean> OIDC_ENABLE = new BooleanConfigKey(
- "oidc.enable",
+ public static final ConfigKey<Boolean> OPENID_FORCE = new BooleanConfigKey(
+ "openid.force",
List.of(KeyType.CONFIG));
/**
- * Force OIDC authentication.
+ * OpenID Connect Client ID.
+ * This is a unique ID assigned to each application you register with your identity provider.
+ * Required to enable SSO.
*/
- public static final ConfigKey<Boolean> OIDC_FORCE = new BooleanConfigKey(
- "oidc.force",
+ public static final ConfigKey<String> OPENID_CLIENTID = new StringConfigKey(
+ "openid.clientId",
List.of(KeyType.CONFIG));
/**
- * OIDC Client ID.
+ * OpenID Connect Client Secret.
+ * This is a secret assigned to each application you register with your identity provider.
+ * Required to enable SSO.
*/
- public static final ConfigKey<String> OIDC_CLIENTID = new StringConfigKey(
- "oidc.clientId",
+ public static final ConfigKey<String> OPENID_CLIENTSECRET = new StringConfigKey(
+ "openid.clientSecret",
List.of(KeyType.CONFIG));
/**
- * OIDC Client Secret.
+ * OpenID Connect Authorization URL.
+ * This can usually be found in the documentation of your identity provider or by using the well-known
+ * configuration endpoint, eg. https://auth.example.com//.well-known/openid-configuration
+ * Required to enable SSO.
*/
- public static final ConfigKey<String> OIDC_CLIENTSECRET = new StringConfigKey(
- "oidc.clientSecret",
+ public static final ConfigKey<String> OPENID_AUTHURL = new StringConfigKey(
+ "openid.authUrl",
List.of(KeyType.CONFIG));
-
/**
- * OIDC Authorization URL.
+ * OpenID Connect Token URL.
+ * This can be found in the same ways at openid.authUrl.
+ * Required to enable SSO.
*/
- public static final ConfigKey<String> OIDC_AUTHURL = new StringConfigKey(
- "oidc.authUrl",
- List.of(KeyType.CONFIG));
- /**
- * OIDC Token URL.
- */
- public static final ConfigKey<String> OIDC_TOKENURL = new StringConfigKey(
- "oidc.tokenUrl",
+ public static final ConfigKey<String> OPENID_TOKENURL = new StringConfigKey(
+ "openid.tokenUrl",
List.of(KeyType.CONFIG));
/**
- * OIDC User Info URL.
+ * OpenID Connect User Info URL.
+ * This can be found in the same ways at openid.authUrl.
+ * Required to enable SSO.
*/
- public static final ConfigKey<String> OIDC_USERINFOURL = new StringConfigKey(
- "oidc.userInfoUrl",
+ public static final ConfigKey<String> OPENID_USERINFOURL = new StringConfigKey(
+ "openid.userInfoUrl",
List.of(KeyType.CONFIG));
/**
- * OIDC group to grant admin access.
+ * OpenID Connect group to grant admin access.
+ * Defaults to admins.
*/
- public static final ConfigKey<String> OIDC_ADMINGROUP = new StringConfigKey(
- "oidc.adminGroup",
- List.of(KeyType.CONFIG));
+ public static final ConfigKey<String> OPENID_ADMINGROUP = new StringConfigKey(
+ "openid.adminGroup",
+ List.of(KeyType.CONFIG),
+ "admins");
/**
* If no data is reported by a device for the given amount of time, status changes from online to unknown. Value is
@@ -1629,7 +1635,7 @@ public final class Keys {
List.of(KeyType.CONFIG));
/**
- * Public URL for the web app. Used for notification and report link.
+ * Public URL for the web app. Used for notification, report link and OpenID Connect.
* If not provided, Traccar will attempt to get a URL from the server IP address, but it might be a local address.
*/
public static final ConfigKey<String> WEB_URL = new StringConfigKey(
diff --git a/src/main/java/org/traccar/api/security/OpenIDProvider.java b/src/main/java/org/traccar/database/OpenIdProvider.java
index 1e18fde43..fc1409d14 100644
--- a/src/main/java/org/traccar/api/security/OpenIDProvider.java
+++ b/src/main/java/org/traccar/database/OpenIdProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 - 2023 Anton Tananaev (anton@traccar.org)
+ * 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.
@@ -13,23 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.traccar.api.security;
+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.Storage;
import org.traccar.storage.StorageException;
-import org.traccar.storage.query.Request;
-import org.traccar.storage.query.Columns;
import org.traccar.helper.LogAction;
import org.traccar.helper.ServletHelper;
import java.net.URI;
import java.net.URISyntaxException;
-import java.util.Date;
-import java.util.List;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.WebApplicationException;
@@ -62,7 +58,7 @@ import com.nimbusds.openid.connect.sdk.AuthenticationRequest;
import com.nimbusds.openid.connect.sdk.claims.UserInfo;
-public class OpenIDProvider {
+public class OpenIdProvider {
public final Boolean force;
private final ClientID clientId;
private final Secret clientSecret;
@@ -73,39 +69,35 @@ public class OpenIDProvider {
private URI baseUrl;
private final String adminGroup;
- private Config config;
private LoginService loginService;
- private Storage storage;
@Inject
- public OpenIDProvider(Config config, LoginService loginService, Storage storage) {
- force = config.getBoolean(Keys.OIDC_FORCE);
- clientId = new ClientID(config.getString(Keys.OIDC_CLIENTID));
- clientSecret = new Secret(config.getString(Keys.OIDC_CLIENTSECRET));
+ 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.config = config;
- this.storage = storage;
this.loginService = loginService;
try {
callbackUrl = new URI(config.getString(Keys.WEB_URL, "") + "/api/session/openid/callback");
- authUrl = new URI(config.getString(Keys.OIDC_AUTHURL, ""));
- tokenUrl = new URI(config.getString(Keys.OIDC_TOKENURL, ""));
- userInfoUrl = new URI(config.getString(Keys.OIDC_USERINFOURL, ""));
+ 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.OIDC_ADMINGROUP);
+ adminGroup = config.getString(Keys.OPENID_ADMINGROUP);
}
- public URI createAuthRequest() {
+ public URI createAuthUri() {
AuthenticationRequest request = new AuthenticationRequest.Builder(
new ResponseType("code"),
new Scope("openid", "profile", "email", "groups"),
- this.clientId,
- this.callbackUrl)
- .endpointURI(this.authUrl)
+ clientId,
+ callbackUrl)
+ .endpointURI(authUrl)
.state(new State())
.nonce(new Nonce())
.build();
@@ -115,10 +107,10 @@ public class OpenIDProvider {
private OIDCTokenResponse getToken(AuthorizationCode code) {
// Credentials to authenticate us to the token endpoint
- ClientAuthentication clientAuth = new ClientSecretBasic(this.clientId, this.clientSecret);
- AuthorizationGrant codeGrant = new AuthorizationCodeGrant(code, this.callbackUrl);
+ ClientAuthentication clientAuth = new ClientSecretBasic(clientId, clientSecret);
+ AuthorizationGrant codeGrant = new AuthorizationCodeGrant(code, callbackUrl);
- TokenRequest request = new TokenRequest(this.tokenUrl, clientAuth, codeGrant);
+ TokenRequest request = new TokenRequest(tokenUrl, clientAuth, codeGrant);
TokenResponse tokenResponse;
try {
@@ -136,37 +128,14 @@ public class OpenIDProvider {
}
}
- private AuthorizationCode parseCallback(URI requri) throws WebApplicationException {
- AuthorizationResponse response;
-
- try {
- response = AuthorizationResponse.parse(requri);
- } catch (ParseException e) {
- return null;
- }
-
- if (!response.indicatesSuccess()) {
- AuthorizationErrorResponse error = response.toErrorResponse();
- throw new WebApplicationException(Response.status(403).entity(error.getErrorObject().getDescription()).build());
- }
-
- return response.toSuccessResponse().getAuthorizationCode();
- }
-
- private UserInfo getUserInfo(BearerAccessToken token) {
+ private UserInfo getUserInfo(BearerAccessToken token) throws IOException, ParseException {
UserInfoResponse userInfoResponse;
- try {
- HTTPResponse httpResponse = new UserInfoRequest(this.userInfoUrl, token)
- .toHTTPRequest()
- .send();
+ HTTPResponse httpResponse = new UserInfoRequest(userInfoUrl, token)
+ .toHTTPRequest()
+ .send();
- userInfoResponse = UserInfoResponse.parse(httpResponse);
- } catch (IOException e) {
- return null;
- } catch (ParseException e) {
- return null;
- }
+ userInfoResponse = UserInfoResponse.parse(httpResponse);
if (!userInfoResponse.indicatesSuccess()) {
// User info request failed - usually from expiring
@@ -176,39 +145,21 @@ public class OpenIDProvider {
return userInfoResponse.toSuccessResponse().getUserInfo();
}
- private User createUser(String name, String email, Boolean administrator) throws StorageException {
- User user = new User();
-
- user.setName(name);
- user.setEmail(email);
- user.setFixedEmail(true);
- user.setDeviceLimit(this.config.getInteger(Keys.USERS_DEFAULT_DEVICE_LIMIT));
+ public Response handleCallback(URI requestUri, HttpServletRequest request) throws StorageException, ParseException, IOException, WebApplicationException {
+ AuthorizationResponse response = AuthorizationResponse.parse(requestUri);
- int expirationDays = this.config.getInteger(Keys.USERS_DEFAULT_EXPIRATION_DAYS);
-
- if (expirationDays > 0) {
- user.setExpirationTime(new Date(System.currentTimeMillis() + expirationDays * 86400000L));
- }
-
- if (administrator) {
- user.setAdministrator(true);
+ if (!response.indicatesSuccess()) {
+ AuthorizationErrorResponse error = response.toErrorResponse();
+ throw new WebApplicationException(Response.status(403).entity(error.getErrorObject().getDescription()).build());
}
- user.setId(this.storage.addObject(user, new Request(new Columns.Exclude("id"))));
-
- return user;
- }
-
- public Response handleCallback(URI requri, HttpServletRequest request) throws StorageException, WebApplicationException {
- // Parse callback
- AuthorizationCode authCode = this.parseCallback(requri);
+ AuthorizationCode authCode = response.toSuccessResponse().getAuthorizationCode();
if (authCode == null) {
return Response.status(403).entity( "Invalid OpenID Connect callback.").build();
}
- // Get token from IDP
- OIDCTokenResponse tokens = this.getToken(authCode);
+ OIDCTokenResponse tokens = getToken(authCode);
if (tokens == null) {
return Response.status(403).entity("Unable to authenticate with the OpenID Connect provider. Please try again.").build();
@@ -216,27 +167,14 @@ public class OpenIDProvider {
BearerAccessToken bearerToken = tokens.getOIDCTokens().getBearerAccessToken();
- // Get user info from IDP
- UserInfo idpUser = this.getUserInfo(bearerToken);
+ UserInfo userInfo = getUserInfo(bearerToken);
- if (idpUser == null) {
+ if (userInfo == null) {
return Response.status(500).entity("Failed to access OpenID Connect user info endpoint. Please contact your administrator.").build();
}
- String email = idpUser.getEmailAddress();
- String name = idpUser.getName();
-
- // Check if user exists
- User user = this.loginService.lookup(email);
-
- // If user does not exist, create one
- if (user == null) {
- List<String> groups = idpUser.getStringListClaim("groups");
- Boolean administrator = groups.contains(this.adminGroup);
- user = this.createUser(name, email, administrator);
- }
+ User user = loginService.login(userInfo.getEmailAddress(), userInfo.getName(), userInfo.getStringListClaim("groups").contains(adminGroup));
- // Set user session and redirect to homepage
request.getSession().setAttribute(SessionResource.USER_ID_KEY, user.getId());
LogAction.login(user.getId(), ServletHelper.retrieveRemoteAddress(request));
return Response.seeOther(
diff --git a/src/main/java/org/traccar/model/Server.java b/src/main/java/org/traccar/model/Server.java
index 4c6e10b5e..b790ca472 100644
--- a/src/main/java/org/traccar/model/Server.java
+++ b/src/main/java/org/traccar/model/Server.java
@@ -17,15 +17,13 @@ package org.traccar.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import org.traccar.Main;
-import org.traccar.api.security.OpenIDProvider;
import org.traccar.storage.QueryIgnore;
import org.traccar.storage.StorageName;
@StorageName("tc_servers")
@JsonIgnoreProperties(ignoreUnknown = true)
public class Server extends ExtendedModel implements UserRestrictions {
-
+
private boolean registration;
public boolean getRegistration() {
@@ -264,15 +262,27 @@ public class Server extends ExtendedModel implements UserRestrictions {
this.newServer = newServer;
}
+ private boolean openIdEnabled;
+
+ @QueryIgnore
+ public boolean getOpenIdEnabled() {
+ return openIdEnabled;
+ }
+
+ @QueryIgnore
+ public void setOpenIdEnabled(boolean openIdEnabled) {
+ this.openIdEnabled = openIdEnabled;
+ }
+
+ private boolean openIdForce;
+
@QueryIgnore
- public boolean getOidcEnabled() {
- OpenIDProvider oidc = Main.getInjector().getInstance(OpenIDProvider.class);
- return oidc != null;
+ public boolean getOpenIdForce() {
+ return openIdForce;
}
@QueryIgnore
- public boolean getOidcForce() {
- OpenIDProvider oidc = Main.getInjector().getInstance(OpenIDProvider.class);
- return oidc != null && oidc.force;
+ public void setOpenIdForce(boolean openIdForce) {
+ this.openIdForce = openIdForce;
}
}