Skip to content

Commit f268d52

Browse files
authored
Add auth service config setting for user's default time zone (#24381)
* Add auth service config setting for user's default time zone The authentication backends for Active Directory, LDAP, OIDC, Okta, and SAML previously set the time zone for +newly synchronized users to the value of the `root_timezone` config file setting. ("UTC" by default) This change introduces a configurable "default user time zone" setting for all authentication backends. The default value is unset, meaning that the browser's time zone will be used by default. Other changes: - Adjust the notification text for AD/LDAP configurations to reflect reality. Default roles are only set for new users, not on login. - Add note to UPGRADING.md * Add changelog entry * Remove unneeded default-user-timezone-select class * Show "Browser's time zone" when no zone is selected
1 parent b9536c5 commit f268d52

File tree

18 files changed

+108
-30
lines changed

18 files changed

+108
-30
lines changed

UPGRADING.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
Upgrading to Graylog 7.1.x
22
==========================
33

4+
## User Session Termination
5+
46
All user sessions will be terminated when upgrading because the internal storage format for sessions has been changed.
57
Users will have to log in again.
68

79
## Breaking Changes
810

9-
tbd
11+
### External Authentication Services: Changed Default User Time Zone
12+
13+
The authentication backends for Active Directory, LDAP, OIDC, Okta, and SAML previously set the time zone for
14+
newly synchronized users to the value of the `root_timezone` config file setting. ("UTC" by default)
15+
16+
Graylog 7.1 introduces a configurable "default user time zone" setting for all authentication backends.
17+
The default value is unset, meaning that the browser's time zone will be used by default.
1018

1119
## Configuration File Changes
1220

changelog/unreleased/pr-24381.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
type = "c"
2+
message = "Add auth service config setting for user's default time zone. (see upgrade notes)"
3+
4+
pulls = ["24381", "graylog-plugin-enterprise#12641"]

graylog2-server/src/main/java/org/graylog/security/authservice/AuthServiceBackendDTO.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
2929
import org.mongojack.ObjectId;
3030

3131
import javax.annotation.Nullable;
32+
import java.time.ZoneId;
3233
import java.util.Collections;
34+
import java.util.Optional;
3335
import java.util.Set;
3436

3537
import static org.apache.commons.lang3.StringUtils.isBlank;
@@ -42,6 +44,7 @@ public abstract class AuthServiceBackendDTO implements BuildableMongoEntity<Auth
4244
public static final String FIELD_TITLE = "title";
4345
public static final String FIELD_DESCRIPTION = "description";
4446
private static final String FIELD_DEFAULT_ROLES = "default_roles";
47+
private static final String FIELD_DEFAULT_USER_TIMEZONE = "default_user_timezone";
4548
private static final String FIELD_CONFIG = "config";
4649

4750
@Id
@@ -59,6 +62,9 @@ public abstract class AuthServiceBackendDTO implements BuildableMongoEntity<Auth
5962
@JsonProperty(FIELD_DEFAULT_ROLES)
6063
public abstract Set<String> defaultRoles();
6164

65+
@JsonProperty(FIELD_DEFAULT_USER_TIMEZONE)
66+
public abstract Optional<ZoneId> defaultUserTimezone();
67+
6268
@NotNull
6369
@JsonProperty(FIELD_CONFIG)
6470
public abstract AuthServiceBackendConfig config();
@@ -96,7 +102,8 @@ public abstract static class Builder implements BuildableMongoEntity.Builder<Aut
96102
public static Builder create() {
97103
return new AutoValue_AuthServiceBackendDTO.Builder()
98104
.description("")
99-
.defaultRoles(Collections.emptySet());
105+
.defaultRoles(Collections.emptySet())
106+
.defaultUserTimezone(null);
100107
}
101108

102109
@Id
@@ -113,6 +120,9 @@ public static Builder create() {
113120
@JsonProperty(FIELD_DEFAULT_ROLES)
114121
public abstract Builder defaultRoles(Set<String> defaultRoles);
115122

123+
@JsonProperty(FIELD_DEFAULT_USER_TIMEZONE)
124+
public abstract Builder defaultUserTimezone(@Nullable ZoneId defaultUserTimezone);
125+
116126
@JsonProperty(FIELD_CONFIG)
117127
public abstract Builder config(AuthServiceBackendConfig config);
118128

graylog2-server/src/main/java/org/graylog/security/authservice/ProvisionerService.java

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717
package org.graylog.security.authservice;
1818

19+
import jakarta.inject.Inject;
1920
import org.graylog2.plugin.database.ValidationException;
2021
import org.graylog2.plugin.database.users.User;
2122
import org.graylog2.shared.users.UserService;
@@ -25,25 +26,19 @@
2526
import org.slf4j.Logger;
2627
import org.slf4j.LoggerFactory;
2728

28-
import jakarta.inject.Inject;
29-
import jakarta.inject.Named;
30-
3129
import java.util.Collections;
3230
import java.util.Map;
3331

3432
public class ProvisionerService {
3533
private static final Logger LOG = LoggerFactory.getLogger(ProvisionerService.class);
3634

3735
private final UserService userService;
38-
private final DateTimeZone rootTimeZone;
3936
private final Map<String, ProvisionerAction.Factory<? extends ProvisionerAction>> provisionerActionFactories;
4037

4138
@Inject
4239
public ProvisionerService(UserService userService,
43-
@Named("root_timezone") DateTimeZone rootTimeZone,
4440
Map<String, ProvisionerAction.Factory<? extends ProvisionerAction>> provisionerActionFactories) {
4541
this.userService = userService;
46-
this.rootTimeZone = rootTimeZone;
4742
this.provisionerActionFactories = provisionerActionFactories;
4843
}
4944

@@ -142,8 +137,10 @@ private User createUser(UserDetails userDetails) {
142137
// Set fields there that should not be overridden by the authentication service provisioning
143138
user.setRoleIds(userDetails.defaultRoles());
144139
user.setPermissions(Collections.emptyList());
145-
// TODO: Does the timezone need to be configurable per auth service backend?
146-
user.setTimeZone(rootTimeZone);
140+
// Default to null for the user's time zone so the UI will use the browser's time zone by default.
141+
user.setTimeZone(userDetails.timezone()
142+
.map(zoneId -> DateTimeZone.forID(zoneId.getId()))
143+
.orElse(null));
147144
// TODO: Does the session timeout need to be configurable per auth service backend?
148145
user.setSessionTimeoutMs(UserConfiguration.DEFAULT_VALUES.globalSessionTimeoutInterval().toMillis());
149146

graylog2-server/src/main/java/org/graylog/security/authservice/UserDetails.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.google.common.collect.ImmutableSet;
2121

2222
import javax.annotation.Nullable;
23+
import java.time.ZoneId;
2324
import java.util.Optional;
2425
import java.util.Set;
2526

@@ -46,6 +47,8 @@ public abstract class UserDetails {
4647

4748
public abstract Optional<String> lastName();
4849

50+
public abstract Optional<ZoneId> timezone();
51+
4952
/**
5053
* Some authentication backends only currently support the fullName attribute (and not firstName and lastName),
5154
* so it is still optionally available here. Prefer use of only firstName and lastName when available.
@@ -98,6 +101,8 @@ public static Builder create() {
98101

99102
public abstract Builder lastName(@Nullable String lastName);
100103

104+
public abstract Builder timezone(@Nullable ZoneId timezone);
105+
101106
/**
102107
* Starting in Graylog 4.1, use of this method is deprecated.
103108
* Prefer use of the {@link #firstName()} and {@link #lastName()} methods instead when possible. This way,
@@ -119,7 +124,7 @@ public UserDetails build() {
119124

120125
// Either a fullName, or a firstName/lastName are required.
121126
final boolean missingFirstOrLast = !userDetails.firstName().isPresent()
122-
|| !userDetails.lastName().isPresent();
127+
|| !userDetails.lastName().isPresent();
123128

124129
if (missingFirstOrLast && !userDetails.fullName().isPresent()) {
125130
throw new IllegalArgumentException("Either a firstName/lastName or a fullName are required.");

graylog2-server/src/main/java/org/graylog/security/authservice/backend/ADAuthServiceBackend.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.unboundid.ldap.sdk.Filter;
2323
import com.unboundid.ldap.sdk.LDAPConnection;
2424
import com.unboundid.ldap.sdk.LDAPException;
25+
import jakarta.inject.Inject;
2526
import org.graylog.security.authservice.AuthServiceBackend;
2627
import org.graylog.security.authservice.AuthServiceBackendDTO;
2728
import org.graylog.security.authservice.AuthServiceCredentials;
@@ -39,9 +40,6 @@
3940
import org.slf4j.LoggerFactory;
4041

4142
import javax.annotation.Nullable;
42-
43-
import jakarta.inject.Inject;
44-
4543
import java.security.GeneralSecurityException;
4644
import java.util.Arrays;
4745
import java.util.Collections;
@@ -122,6 +120,7 @@ public Optional<AuthenticationDetails> authenticateAndProvision(AuthServiceCrede
122120
.fullName(userEntry.fullName())
123121
.email(userEntry.email())
124122
.defaultRoles(backend.defaultRoles())
123+
.timezone(backend.defaultUserTimezone().orElse(null))
125124
.build());
126125

127126
return Optional.of(AuthenticationDetails.builder().userDetails(userDetails).build());

graylog2-server/src/main/java/org/graylog/security/authservice/backend/LDAPAuthServiceBackend.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.google.inject.assistedinject.Assisted;
2222
import com.unboundid.ldap.sdk.LDAPConnection;
2323
import com.unboundid.ldap.sdk.LDAPException;
24+
import jakarta.inject.Inject;
2425
import org.graylog.security.authservice.AuthServiceBackend;
2526
import org.graylog.security.authservice.AuthServiceBackendDTO;
2627
import org.graylog.security.authservice.AuthServiceCredentials;
@@ -38,9 +39,6 @@
3839
import org.slf4j.LoggerFactory;
3940

4041
import javax.annotation.Nullable;
41-
42-
import jakarta.inject.Inject;
43-
4442
import java.security.GeneralSecurityException;
4543
import java.util.Collections;
4644
import java.util.HashMap;
@@ -102,6 +100,7 @@ public Optional<AuthenticationDetails> authenticateAndProvision(AuthServiceCrede
102100
.fullName(userEntry.fullName())
103101
.email(userEntry.email())
104102
.defaultRoles(backend.defaultRoles())
103+
.timezone(backend.defaultUserTimezone().orElse(null))
105104
.build());
106105

107106
return Optional.of(AuthenticationDetails.builder().userDetails(userDetails).build());

graylog2-server/src/test/java/org/graylog/security/authservice/ProvisionerServiceTest.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import static org.junit.jupiter.api.Assertions.assertNotNull;
3636
import static org.mockito.ArgumentMatchers.eq;
3737
import static org.mockito.ArgumentMatchers.isA;
38+
import static org.mockito.ArgumentMatchers.isNull;
3839
import static org.mockito.Mockito.mock;
3940
import static org.mockito.Mockito.times;
4041
import static org.mockito.Mockito.verify;
@@ -64,7 +65,7 @@ public class ProvisionerServiceTest {
6465

6566
@BeforeEach
6667
public void setUp() throws Exception {
67-
provisionerService = new ProvisionerService(userService, DateTimeZone.UTC, new HashMap<>());
68+
provisionerService = new ProvisionerService(userService, new HashMap<>());
6869
}
6970

7071
@Test
@@ -90,6 +91,7 @@ public void testFirstLastNameOnlySuccess() throws ValidationException {
9091
provisionerService.provision(userDetails);
9192
verify(userService, times(1)).save(isA(User.class));
9293
verify(user, times(1)).setFirstLastFullNames(eq(FIRST_NAME), eq(LAST_NAME));
94+
verify(user, times(1)).setTimeZone((DateTimeZone) isNull());
9395
}
9496

9597
@Test
@@ -114,5 +116,6 @@ public void testFullNameOnlySuccess() throws ValidationException {
114116
provisionerService.provision(userDetails);
115117
verify(userService, times(1)).save(isA(User.class));
116118
verify(user, times(1)).setFullName(FULL_NAME);
119+
verify(user, times(1)).setTimeZone((DateTimeZone) isNull());
117120
}
118121
}

graylog2-web-interface/src/components/authentication/directoryServices/BackendConfigDetails/UserSyncSection.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const UserSyncSection = ({ authenticationBackend, roles, excludedFields = {} }:
4949
userUniqueIdAttribute,
5050
emailAttributes,
5151
} = authenticationBackend.config;
52-
const { defaultRoles = Immutable.List() } = authenticationBackend;
52+
const { defaultRoles = Immutable.List(), defaultUserTimezone } = authenticationBackend;
5353

5454
return (
5555
<SectionComponent
@@ -66,6 +66,7 @@ const UserSyncSection = ({ authenticationBackend, roles, excludedFields = {} }:
6666
<ReadOnlyFormGroup label="ID Attribute" value={userUniqueIdAttribute} />
6767
)}
6868
<ReadOnlyFormGroup label="Default Roles" value={rolesList(defaultRoles, roles)} />
69+
<ReadOnlyFormGroup label="Default User Time Zone" value={defaultUserTimezone || "Browser's time zone"} />
6970
</SectionComponent>
7071
);
7172
};

graylog2-web-interface/src/components/authentication/directoryServices/BackendWizard/BackendWizard.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ const _prepareSubmitPayload =
115115
const formValues = overrideFormValues ?? getUpdatedFormsValues();
116116
const {
117117
defaultRoles = '',
118+
defaultUserTimezone,
118119
description,
119120
serverHost,
120121
serverPort,
@@ -136,6 +137,7 @@ const _prepareSubmitPayload =
136137
title,
137138
description,
138139
default_roles: defaultRoles.split(','),
140+
default_user_timezone: defaultUserTimezone,
139141
config: {
140142
servers: [{ host: serverHost, port: serverPort }],
141143
system_user_dn: systemUserDn,

0 commit comments

Comments
 (0)