CVE-2021-45890 (Java, published 2021-12-27T20:15Z)
Description: value: basic/BasicAuthProvider.java in AuthGuard before 0.9.0 allows authentication via an inactive identifier.
Severity: HIGH (CVSS v3 base score: 9.8)
Weakness: CWE-287 \u2014 Improper Authentication
When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.
Repository: https://github.com/AuthGuard/AuthGuard
Files changed:
basic-auth/src/main/java/com/nexblocks/authguard/basic/BasicAuthProvider.java
basic-auth/src/test/java/com/nexblocks/authguard/basic/BasicAuthProviderTest.java
service-api/src/main/java/com/nexblocks/authguard/service/exceptions/codes/ErrorCode.java
service-api/src/main/java/com/nexblocks/authguard/service/model/UserIdentifier.java
Commit message: Prevent authentication with inactive identifiers


Vulnerable code:
import com.nexblocks.authguard.service.model.AccountBO;
import com.nexblocks.authguard.service.model.AuthRequestBO;
import com.nexblocks.authguard.service.model.CredentialsBO;
import com.nexblocks.authguard.service.model.EntityType;
    boolean isActive();

Fixed code:
import com.nexblocks.authguard.service.model.*;
        // TODO replace this with Either mapping
            final Optional<Exception> validationError = checkIdentifier(credentials.get(), username);

            if (validationError.isPresent()) {
                return Either.left(validationError.get());
            }

            final Optional<Exception> validationError = checkIdentifier(credentials.get(), username);

            if (validationError.isPresent()) {
                return Either.left(validationError.get());
            }

    private Optional<Exception> checkIdentifier(final CredentialsBO credentials,
                                                final String identifier) {
        final Optional<UserIdentifierBO> matchedIdentifier = credentials.getIdentifiers()
                .stream()
                .filter(existing -> identifier.equals(existing.getIdentifier()))
                .findFirst();

        if (matchedIdentifier.isEmpty()) {
            return Optional.of(new IllegalStateException("No identifier matched but credentials were returned"));
        }

        if (!matchedIdentifier.get().isActive()) {
            return Optional.of(new ServiceAuthorizationException(ErrorCode.INACTIVE_IDENTIFIER,
                    "Identifier is not active", EntityType.ACCOUNT, credentials.getAccountId()));
        }

        return Optional.empty();
    }

                        .active(true)
    @Test
    void authenticateInactiveIdentifier() {
        final String username = "username";
        final String password = "password";
        final String authorization = Base64.getEncoder().encodeToString((username + ":" + password).getBytes());

        final CredentialsBO credentials = RANDOM.nextObject(CredentialsBO.class)
                .withIdentifiers(UserIdentifierBO.builder()
                        .identifier(username)
                        .type(UserIdentifier.Type.USERNAME)
                        .active(false)
                        .build());

        Mockito.when(credentialsService.getByUsernameUnsafe(username)).thenReturn(Optional.of(credentials));

        final Either<Exception, AccountBO> result = basicAuth.authenticateAndGetAccount(authorization);

        assertThat(result.isLeft()).isTrue();
        assertThat(result.getLeft()).isInstanceOf(ServiceAuthorizationException.class);
    }

    INACTIVE_IDENTIFIER("AT.033"),

    @Value.Default
    default boolean isActive() {
        return true;
    }