Add support to disable two-factor authentication (#6242)

#### What type of PR is this?

/kind improvement
/area core
/milestone 2.17.0

#### What this PR does / why we need it:

This PR provides a configuration property to control whether two-factor authentication is disabled. e.g.:

```yaml
halo:
  security:
    two-factor-auth:
      disabled: true | false # Default is false.
```

#### Which issue(s) this PR fixes:

Fixes #5640 

#### Special notes for your reviewer:

1. Enable 2FA and configure TOTP
2. Disable 2FA by configuring property above
3. Restart Halo and try to login

#### Does this PR introduce a user-facing change?

```release-note
支持通过配置的方式全局禁用二步验证
```
pull/6279/head
John Niang 2024-07-01 17:57:17 +08:00 committed by GitHub
parent 0b7b74e826
commit cc3564bf82
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 45 additions and 4 deletions

View File

@ -127,8 +127,12 @@ public class WebServerSecurityConfig {
@Bean
DefaultUserDetailService userDetailsService(UserService userService,
RoleService roleService) {
return new DefaultUserDetailService(userService, roleService);
RoleService roleService,
HaloProperties haloProperties) {
var userDetailService = new DefaultUserDetailService(userService, roleService);
var twoFactorAuthDisabled = haloProperties.getSecurity().getTwoFactorAuth().isDisabled();
userDetailService.setTwoFactorAuthDisabled(twoFactorAuthDisabled);
return userDetailService;
}
@Bean

View File

@ -16,6 +16,18 @@ public class SecurityProperties {
private final RememberMeOptions rememberMe = new RememberMeOptions();
private final TwoFactorAuthOptions twoFactorAuth = new TwoFactorAuthOptions();
@Data
public static class TwoFactorAuthOptions {
/**
* Whether two-factor authentication is disabled.
*/
private boolean disabled;
}
@Data
public static class FrameOptions {

View File

@ -7,6 +7,7 @@ import static run.halo.app.security.authorization.AuthorityUtils.ANONYMOUS_ROLE_
import static run.halo.app.security.authorization.AuthorityUtils.AUTHENTICATED_ROLE_NAME;
import static run.halo.app.security.authorization.AuthorityUtils.ROLE_PREFIX;
import lombok.Setter;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.ReactiveUserDetailsPasswordService;
@ -31,6 +32,12 @@ public class DefaultUserDetailService
private final RoleService roleService;
/**
* Indicates whether two-factor authentication is disabled.
*/
@Setter
private boolean twoFactorAuthDisabled;
public DefaultUserDetailService(UserService userService, RoleService roleService) {
this.userService = userService;
this.roleService = roleService;
@ -66,7 +73,9 @@ public class DefaultUserDetailService
return setAuthorities.then(Mono.fromSupplier(() -> {
var twoFactorAuthSettings = TwoFactorUtils.getTwoFactorAuthSettings(user);
return new HaloUser.Builder(userBuilder.build())
.twoFactorAuthEnabled(twoFactorAuthSettings.isAvailable())
.twoFactorAuthEnabled(
(!twoFactorAuthDisabled) && twoFactorAuthSettings.isAvailable()
)
.totpEncryptedSecret(user.getSpec().getTotpEncryptedSecret())
.build();
}));

View File

@ -156,11 +156,27 @@ class DefaultUserDetailServiceTest {
.verifyComplete();
}
@Test
void shouldFindHaloUserDetailsWith2faDisabledWhen2faDisabledGlobally() {
userDetailService.setTwoFactorAuthDisabled(true);
var fakeUser = createFakeUser();
fakeUser.getSpec().setTwoFactorAuthEnabled(true);
fakeUser.getSpec().setTotpEncryptedSecret("fake-totp-encrypted-secret");
when(userService.getUser("faker")).thenReturn(Mono.just(fakeUser));
when(roleService.listRoleRefs(any())).thenReturn(Flux.empty());
userDetailService.findByUsername("faker")
.as(StepVerifier::create)
.assertNext(userDetails -> {
assertInstanceOf(HaloUserDetails.class, userDetails);
assertFalse(((HaloUserDetails) userDetails).isTwoFactorAuthEnabled());
})
.verifyComplete();
}
@Test
void shouldFindUserDetailsByExistingUsernameButKindOfRoleRefIsNotRole() {
var foundUser = createFakeUser();
var roleGvk = new Role().groupVersionKind();
var roleRef = new RoleRef();
roleRef.setKind("FakeRole");
roleRef.setApiGroup("fake.halo.run");