Skip to content

Commit 4159460

Browse files
committed
feat: Make allowed domain configurable and add unit tests
Replace hardcoded ALLOWED_DOMAIN constant with @Value-injected property (registration.guard.allowed-domain, defaults to @example.com). Add 10 unit tests covering all code paths for DomainRegistrationGuard.
1 parent 87b4201 commit 4159460

2 files changed

Lines changed: 103 additions & 5 deletions

File tree

src/main/java/com/digitalsanctuary/spring/demo/registration/DomainRegistrationGuard.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.util.Locale;
44

5+
import org.springframework.beans.factory.annotation.Value;
56
import org.springframework.context.annotation.Profile;
67
import org.springframework.stereotype.Component;
78

@@ -13,8 +14,8 @@
1314
import lombok.extern.slf4j.Slf4j;
1415

1516
/**
16-
* Sample {@link RegistrationGuard} that restricts form and passwordless registration to
17-
* {@code @example.com} email addresses while allowing all OAuth2/OIDC registrations.
17+
* Sample {@link RegistrationGuard} that restricts form and passwordless registration to a
18+
* configurable email domain while allowing all OAuth2/OIDC registrations.
1819
*
1920
* <p>This guard is only active when the {@code registration-guard} Spring profile is enabled.
2021
* To try it out, add {@code registration-guard} to your active profiles:</p>
@@ -23,6 +24,9 @@
2324
* ./gradlew bootRun --args='--spring.profiles.active=local,registration-guard'
2425
* </pre>
2526
*
27+
* <p>The allowed domain can be configured via the {@code registration.guard.allowed-domain}
28+
* property (defaults to {@code @example.com}).</p>
29+
*
2630
* <p>See the
2731
* <a href="https://github.com/devondragon/SpringUserFramework/blob/main/REGISTRATION-GUARD.md">
2832
* Registration Guard documentation</a> for the full SPI reference.</p>
@@ -36,7 +40,12 @@
3640
@Profile("registration-guard")
3741
public class DomainRegistrationGuard implements RegistrationGuard {
3842

39-
private static final String ALLOWED_DOMAIN = "@example.com";
43+
private final String allowedDomain;
44+
45+
public DomainRegistrationGuard(
46+
@Value("${registration.guard.allowed-domain:@example.com}") String allowedDomain) {
47+
this.allowedDomain = allowedDomain.toLowerCase(Locale.ROOT);
48+
}
4049

4150
@Override
4251
public RegistrationDecision evaluate(RegistrationContext context) {
@@ -51,13 +60,13 @@ public RegistrationDecision evaluate(RegistrationContext context) {
5160
}
5261

5362
// For form/passwordless, restrict to the allowed domain
54-
if (context.email() != null && context.email().toLowerCase(Locale.ROOT).endsWith(ALLOWED_DOMAIN)) {
63+
if (context.email() != null && context.email().toLowerCase(Locale.ROOT).endsWith(allowedDomain)) {
5564
log.debug("Allowing registration for approved domain: {}", context.email());
5665
return RegistrationDecision.allow();
5766
}
5867

5968
log.info("Denied registration for: {} (domain not allowed)", context.email());
6069
return RegistrationDecision.deny(
61-
"Registration is restricted to " + ALLOWED_DOMAIN + " email addresses.");
70+
"Registration is restricted to " + allowedDomain + " email addresses.");
6271
}
6372
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package com.digitalsanctuary.spring.demo.registration;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertFalse;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
6+
7+
import org.junit.jupiter.api.Test;
8+
9+
import com.digitalsanctuary.spring.user.registration.RegistrationContext;
10+
import com.digitalsanctuary.spring.user.registration.RegistrationDecision;
11+
import com.digitalsanctuary.spring.user.registration.RegistrationSource;
12+
13+
class DomainRegistrationGuardTest {
14+
15+
private final DomainRegistrationGuard guard = new DomainRegistrationGuard("@example.com");
16+
17+
@Test
18+
void formRegistrationWithAllowedDomainIsAllowed() {
19+
RegistrationContext context = new RegistrationContext("user@example.com", RegistrationSource.FORM, null);
20+
RegistrationDecision decision = guard.evaluate(context);
21+
assertTrue(decision.allowed());
22+
}
23+
24+
@Test
25+
void formRegistrationWithDisallowedDomainIsDenied() {
26+
RegistrationContext context = new RegistrationContext("user@other.com", RegistrationSource.FORM, null);
27+
RegistrationDecision decision = guard.evaluate(context);
28+
assertFalse(decision.allowed());
29+
assertTrue(decision.reason().contains("@example.com"));
30+
}
31+
32+
@Test
33+
void passwordlessRegistrationWithAllowedDomainIsAllowed() {
34+
RegistrationContext context = new RegistrationContext("user@example.com", RegistrationSource.PASSWORDLESS, null);
35+
RegistrationDecision decision = guard.evaluate(context);
36+
assertTrue(decision.allowed());
37+
}
38+
39+
@Test
40+
void passwordlessRegistrationWithDisallowedDomainIsDenied() {
41+
RegistrationContext context = new RegistrationContext("user@other.com", RegistrationSource.PASSWORDLESS, null);
42+
RegistrationDecision decision = guard.evaluate(context);
43+
assertFalse(decision.allowed());
44+
}
45+
46+
@Test
47+
void oauth2RegistrationIsAlwaysAllowed() {
48+
RegistrationContext context = new RegistrationContext("user@other.com", RegistrationSource.OAUTH2, "google");
49+
RegistrationDecision decision = guard.evaluate(context);
50+
assertTrue(decision.allowed());
51+
}
52+
53+
@Test
54+
void oidcRegistrationIsAlwaysAllowed() {
55+
RegistrationContext context = new RegistrationContext("user@other.com", RegistrationSource.OIDC, "keycloak");
56+
RegistrationDecision decision = guard.evaluate(context);
57+
assertTrue(decision.allowed());
58+
}
59+
60+
@Test
61+
void nullEmailIsDenied() {
62+
RegistrationContext context = new RegistrationContext(null, RegistrationSource.FORM, null);
63+
RegistrationDecision decision = guard.evaluate(context);
64+
assertFalse(decision.allowed());
65+
}
66+
67+
@Test
68+
void domainCheckIsCaseInsensitive() {
69+
RegistrationContext context = new RegistrationContext("user@EXAMPLE.COM", RegistrationSource.FORM, null);
70+
RegistrationDecision decision = guard.evaluate(context);
71+
assertTrue(decision.allowed());
72+
}
73+
74+
@Test
75+
void customDomainIsRespected() {
76+
DomainRegistrationGuard customGuard = new DomainRegistrationGuard("@mycompany.org");
77+
RegistrationContext allowed = new RegistrationContext("user@mycompany.org", RegistrationSource.FORM, null);
78+
RegistrationContext denied = new RegistrationContext("user@example.com", RegistrationSource.FORM, null);
79+
assertTrue(customGuard.evaluate(allowed).allowed());
80+
assertFalse(customGuard.evaluate(denied).allowed());
81+
}
82+
83+
@Test
84+
void configuredDomainIsCaseInsensitive() {
85+
DomainRegistrationGuard upperGuard = new DomainRegistrationGuard("@EXAMPLE.COM");
86+
RegistrationContext context = new RegistrationContext("user@example.com", RegistrationSource.FORM, null);
87+
assertTrue(upperGuard.evaluate(context).allowed());
88+
}
89+
}

0 commit comments

Comments
 (0)