aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/traccar/api/security
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/traccar/api/security')
-rw-r--r--src/main/java/org/traccar/api/security/CodeRequiredException.java22
-rw-r--r--src/main/java/org/traccar/api/security/LoginResult.java29
-rw-r--r--src/main/java/org/traccar/api/security/LoginService.java66
-rw-r--r--src/main/java/org/traccar/api/security/PermissionsService.java30
-rw-r--r--src/main/java/org/traccar/api/security/SecurityRequestFilter.java66
-rw-r--r--src/main/java/org/traccar/api/security/UserPrincipal.java11
-rw-r--r--src/main/java/org/traccar/api/security/UserSecurityContext.java2
7 files changed, 173 insertions, 53 deletions
diff --git a/src/main/java/org/traccar/api/security/CodeRequiredException.java b/src/main/java/org/traccar/api/security/CodeRequiredException.java
new file mode 100644
index 000000000..d522c6540
--- /dev/null
+++ b/src/main/java/org/traccar/api/security/CodeRequiredException.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2023 Anton Tananaev (anton@traccar.org)
+ *
+ * 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.api.security;
+
+public class CodeRequiredException extends SecurityException {
+ public CodeRequiredException() {
+ super("Code not provided");
+ }
+}
diff --git a/src/main/java/org/traccar/api/security/LoginResult.java b/src/main/java/org/traccar/api/security/LoginResult.java
new file mode 100644
index 000000000..1fccc36d1
--- /dev/null
+++ b/src/main/java/org/traccar/api/security/LoginResult.java
@@ -0,0 +1,29 @@
+package org.traccar.api.security;
+
+import org.traccar.model.User;
+
+import java.util.Date;
+
+public class LoginResult {
+
+ private final User user;
+ private final Date expiration;
+
+ public LoginResult(User user) {
+ this(user, null);
+ }
+
+ public LoginResult(User user, Date expiration) {
+ this.user = user;
+ this.expiration = expiration;
+ }
+
+ public User getUser() {
+ return user;
+ }
+
+ public Date getExpiration() {
+ return expiration;
+ }
+
+}
diff --git a/src/main/java/org/traccar/api/security/LoginService.java b/src/main/java/org/traccar/api/security/LoginService.java
index 88bafcfb5..930c4fa46 100644
--- a/src/main/java/org/traccar/api/security/LoginService.java
+++ b/src/main/java/org/traccar/api/security/LoginService.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2022 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,10 +15,12 @@
*/
package org.traccar.api.security;
+import com.warrenstrange.googleauth.GoogleAuthenticator;
import org.traccar.api.signature.TokenManager;
import org.traccar.config.Config;
import org.traccar.config.Keys;
import org.traccar.database.LdapProvider;
+import org.traccar.helper.model.UserUtil;
import org.traccar.model.User;
import org.traccar.storage.Storage;
import org.traccar.storage.StorageException;
@@ -26,46 +28,54 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.annotation.Nullable;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.annotation.Nullable;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
import java.io.IOException;
import java.security.GeneralSecurityException;
@Singleton
public class LoginService {
+ private final Config config;
private final Storage storage;
private final TokenManager tokenManager;
private final LdapProvider ldapProvider;
private final String serviceAccountToken;
private final boolean forceLdap;
+ private final boolean forceOpenId;
@Inject
public LoginService(
Config config, Storage storage, TokenManager tokenManager, @Nullable LdapProvider ldapProvider) {
this.storage = storage;
+ this.config = config;
this.tokenManager = tokenManager;
this.ldapProvider = ldapProvider;
serviceAccountToken = config.getString(Keys.WEB_SERVICE_ACCOUNT_TOKEN);
forceLdap = config.getBoolean(Keys.LDAP_FORCE);
+ forceOpenId = config.getBoolean(Keys.OPENID_FORCE);
}
- public User login(String token) throws StorageException, GeneralSecurityException, IOException {
+ public LoginResult login(String token) throws StorageException, GeneralSecurityException, IOException {
if (serviceAccountToken != null && serviceAccountToken.equals(token)) {
- return new ServiceAccountUser();
+ return new LoginResult(new ServiceAccountUser());
}
- long userId = tokenManager.verifyToken(token);
+ TokenManager.TokenData tokenData = tokenManager.verifyToken(token);
User user = storage.getObject(User.class, new Request(
- new Columns.All(), new Condition.Equals("id", userId)));
+ new Columns.All(), new Condition.Equals("id", tokenData.getUserId())));
if (user != null) {
checkUserEnabled(user);
}
- return user;
+ return new LoginResult(user, tokenData.getExpiration());
}
- public User login(String email, String password) throws StorageException {
+ public LoginResult login(String email, String password, Integer code) throws StorageException {
+ if (forceOpenId) {
+ return null;
+ }
+
email = email.trim();
User user = storage.getObject(User.class, new Request(
new Columns.All(),
@@ -75,20 +85,39 @@ public class LoginService {
if (user != null) {
if (ldapProvider != null && user.getLogin() != null && ldapProvider.login(user.getLogin(), password)
|| !forceLdap && user.isPasswordValid(password)) {
+ checkUserCode(user, code);
checkUserEnabled(user);
- return user;
+ return new LoginResult(user);
}
} else {
if (ldapProvider != null && ldapProvider.login(email, password)) {
user = ldapProvider.getUser(email);
user.setId(storage.addObject(user, new Request(new Columns.Exclude("id"))));
checkUserEnabled(user);
- return user;
+ return new LoginResult(user);
}
}
return null;
}
+ public LoginResult 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)));
+
+ if (user == null) {
+ user = new User();
+ UserUtil.setUserDefaults(user, config);
+ 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 new LoginResult(user);
+ }
+
private void checkUserEnabled(User user) throws SecurityException {
if (user == null) {
throw new SecurityException("Unknown account");
@@ -96,4 +125,17 @@ public class LoginService {
user.checkDisabled();
}
+ private void checkUserCode(User user, Integer code) throws SecurityException {
+ String key = user.getTotpKey();
+ if (key != null && !key.isEmpty()) {
+ if (code == null) {
+ throw new CodeRequiredException();
+ }
+ GoogleAuthenticator authenticator = new GoogleAuthenticator();
+ if (!authenticator.authorize(key, code)) {
+ throw new SecurityException("User authorization failed");
+ }
+ }
+ }
+
}
diff --git a/src/main/java/org/traccar/api/security/PermissionsService.java b/src/main/java/org/traccar/api/security/PermissionsService.java
index 4421572d7..d60bbafb8 100644
--- a/src/main/java/org/traccar/api/security/PermissionsService.java
+++ b/src/main/java/org/traccar/api/security/PermissionsService.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2022 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,7 +23,8 @@ import org.traccar.model.Device;
import org.traccar.model.Group;
import org.traccar.model.GroupedModel;
import org.traccar.model.ManagedUser;
-import org.traccar.model.ScheduledModel;
+import org.traccar.model.Notification;
+import org.traccar.model.Schedulable;
import org.traccar.model.Server;
import org.traccar.model.User;
import org.traccar.model.UserRestrictions;
@@ -33,7 +34,7 @@ import org.traccar.storage.query.Columns;
import org.traccar.storage.query.Condition;
import org.traccar.storage.query.Request;
-import javax.inject.Inject;
+import jakarta.inject.Inject;
import java.util.Objects;
@RequestScoped
@@ -129,17 +130,17 @@ public class PermissionsService {
GroupedModel before = null;
if (!addition) {
before = storage.getObject(after.getClass(), new Request(
- new Columns.Include("groupId"), new Condition.Equals("id", object.getId())));
+ new Columns.Include("groupId"), new Condition.Equals("id", after.getId())));
}
if (before == null || before.getGroupId() != after.getGroupId()) {
checkPermission(Group.class, userId, after.getGroupId());
}
}
}
- if (object instanceof ScheduledModel) {
- ScheduledModel after = ((ScheduledModel) object);
+ if (object instanceof Schedulable) {
+ Schedulable after = ((Schedulable) object);
if (after.getCalendarId() > 0) {
- ScheduledModel before = null;
+ Schedulable before = null;
if (!addition) {
before = storage.getObject(after.getClass(), new Request(
new Columns.Include("calendarId"), new Condition.Equals("id", object.getId())));
@@ -149,6 +150,19 @@ public class PermissionsService {
}
}
}
+ if (object instanceof Notification) {
+ Notification after = ((Notification) object);
+ if (after.getCommandId() > 0) {
+ Notification before = null;
+ if (!addition) {
+ before = storage.getObject(after.getClass(), new Request(
+ new Columns.Include("commandId"), new Condition.Equals("id", object.getId())));
+ }
+ if (before == null || before.getCommandId() != after.getCommandId()) {
+ checkPermission(Command.class, userId, after.getCommandId());
+ }
+ }
+ }
}
}
@@ -167,7 +181,7 @@ public class PermissionsService {
|| before.getUserLimit() != after.getUserLimit()) {
checkAdmin(userId);
}
- User user = getUser(userId);
+ User user = userId > 0 ? getUser(userId) : null;
if (user != null && user.getExpirationTime() != null
&& !Objects.equals(before.getExpirationTime(), after.getExpirationTime())
&& (after.getExpirationTime() == null
diff --git a/src/main/java/org/traccar/api/security/SecurityRequestFilter.java b/src/main/java/org/traccar/api/security/SecurityRequestFilter.java
index 94b6bbf05..12a5dbecf 100644
--- a/src/main/java/org/traccar/api/security/SecurityRequestFilter.java
+++ b/src/main/java/org/traccar/api/security/SecurityRequestFilter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2022 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
*/
package org.traccar.api.security;
+import com.google.inject.Injector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.traccar.api.resource.SessionResource;
@@ -23,32 +24,26 @@ import org.traccar.helper.DataConverter;
import org.traccar.model.User;
import org.traccar.storage.StorageException;
-import javax.annotation.security.PermitAll;
-import javax.inject.Inject;
-import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.WebApplicationException;
-import javax.ws.rs.container.ContainerRequestContext;
-import javax.ws.rs.container.ContainerRequestFilter;
-import javax.ws.rs.container.ResourceInfo;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.SecurityContext;
+import jakarta.annotation.security.PermitAll;
+import jakarta.inject.Inject;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.container.ContainerRequestContext;
+import jakarta.ws.rs.container.ContainerRequestFilter;
+import jakarta.ws.rs.container.ResourceInfo;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.SecurityContext;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
+import java.util.Date;
public class SecurityRequestFilter implements ContainerRequestFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(SecurityRequestFilter.class);
- public static final String AUTHORIZATION_HEADER = "Authorization";
- public static final String WWW_AUTHENTICATE = "WWW-Authenticate";
- public static final String BASIC_REALM = "Basic realm=\"api\"";
- public static final String BEARER_PREFIX = "Bearer ";
- public static final String X_REQUESTED_WITH = "X-Requested-With";
- public static final String XML_HTTP_REQUEST = "XMLHttpRequest";
-
public static String[] decodeBasicAuth(String auth) {
auth = auth.replaceFirst("[B|b]asic ", "");
byte[] decodedBytes = DataConverter.parseBase64(auth);
@@ -70,6 +65,9 @@ public class SecurityRequestFilter implements ContainerRequestFilter {
@Inject
private StatisticsManager statisticsManager;
+ @Inject
+ private Injector injector;
+
@Override
public void filter(ContainerRequestContext requestContext) {
@@ -81,20 +79,22 @@ public class SecurityRequestFilter implements ContainerRequestFilter {
try {
- String authHeader = requestContext.getHeaderString(AUTHORIZATION_HEADER);
+ String authHeader = requestContext.getHeaderString("Authorization");
if (authHeader != null) {
try {
- User user;
- if (authHeader.startsWith(BEARER_PREFIX)) {
- user = loginService.login(authHeader.substring(BEARER_PREFIX.length()));
+ LoginResult loginResult;
+ if (authHeader.startsWith("Bearer ")) {
+ loginResult = loginService.login(authHeader.substring(7));
} else {
String[] auth = decodeBasicAuth(authHeader);
- user = loginService.login(auth[0], auth[1]);
+ loginResult = loginService.login(auth[0], auth[1], null);
}
- if (user != null) {
+ if (loginResult != null) {
+ User user = loginResult.getUser();
statisticsManager.registerRequest(user.getId());
- securityContext = new UserSecurityContext(new UserPrincipal(user.getId()));
+ securityContext = new UserSecurityContext(
+ new UserPrincipal(user.getId(), loginResult.getExpiration()));
}
} catch (StorageException | GeneralSecurityException | IOException e) {
throw new WebApplicationException(e);
@@ -103,14 +103,19 @@ public class SecurityRequestFilter implements ContainerRequestFilter {
} else if (request.getSession() != null) {
Long userId = (Long) request.getSession().getAttribute(SessionResource.USER_ID_KEY);
+ Date expiration = (Date) request.getSession().getAttribute(SessionResource.EXPIRATION_KEY);
if (userId != null) {
- statisticsManager.registerRequest(userId);
- securityContext = new UserSecurityContext(new UserPrincipal(userId));
+ User user = injector.getInstance(PermissionsService.class).getUser(userId);
+ if (user != null) {
+ user.checkDisabled();
+ statisticsManager.registerRequest(userId);
+ securityContext = new UserSecurityContext(new UserPrincipal(userId, expiration));
+ }
}
}
- } catch (SecurityException e) {
+ } catch (SecurityException | StorageException e) {
LOGGER.warn("Authentication error", e);
}
@@ -120,8 +125,9 @@ public class SecurityRequestFilter implements ContainerRequestFilter {
Method method = resourceInfo.getResourceMethod();
if (!method.isAnnotationPresent(PermitAll.class)) {
Response.ResponseBuilder responseBuilder = Response.status(Response.Status.UNAUTHORIZED);
- if (!XML_HTTP_REQUEST.equals(request.getHeader(X_REQUESTED_WITH))) {
- responseBuilder.header(WWW_AUTHENTICATE, BASIC_REALM);
+ String accept = request.getHeader("Accept");
+ if (accept != null && accept.contains("text/html")) {
+ responseBuilder.header("WWW-Authenticate", "Basic realm=\"api\"");
}
throw new WebApplicationException(responseBuilder.build());
}
diff --git a/src/main/java/org/traccar/api/security/UserPrincipal.java b/src/main/java/org/traccar/api/security/UserPrincipal.java
index 18b84a0e1..83bd06fe9 100644
--- a/src/main/java/org/traccar/api/security/UserPrincipal.java
+++ b/src/main/java/org/traccar/api/security/UserPrincipal.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 - 2020 Anton Tananaev (anton@traccar.org)
+ * Copyright 2015 - 2023 Anton Tananaev (anton@traccar.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,19 +16,26 @@
package org.traccar.api.security;
import java.security.Principal;
+import java.util.Date;
public class UserPrincipal implements Principal {
private final long userId;
+ private final Date expiration;
- public UserPrincipal(long userId) {
+ public UserPrincipal(long userId, Date expiration) {
this.userId = userId;
+ this.expiration = expiration;
}
public Long getUserId() {
return userId;
}
+ public Date getExpiration() {
+ return expiration;
+ }
+
@Override
public String getName() {
return null;
diff --git a/src/main/java/org/traccar/api/security/UserSecurityContext.java b/src/main/java/org/traccar/api/security/UserSecurityContext.java
index 97df6b6c7..f7adeac64 100644
--- a/src/main/java/org/traccar/api/security/UserSecurityContext.java
+++ b/src/main/java/org/traccar/api/security/UserSecurityContext.java
@@ -15,7 +15,7 @@
*/
package org.traccar.api.security;
-import javax.ws.rs.core.SecurityContext;
+import jakarta.ws.rs.core.SecurityContext;
import java.security.Principal;
public class UserSecurityContext implements SecurityContext {