Skip to content

Commit

Permalink
feat: anonymous user (artipie#1340)
Browse files Browse the repository at this point in the history
* Anonymous user

* fix errors

* fix errors

* fix errors

* fix errors

* fix errors

* fix errors

* fix codestyle

* fix codestyle

* description

* fix tests

* Remove 'ANY' user

* Remove 'ANY' user

* fix failed test

* fix failed test

* fix failed test

* fix failed test

* fix failed test

* fix failed test

* extended log

* fix failed tests

* disable flaky test

* fix code style

* fix failed test

* fix failed test
  • Loading branch information
dgarus committed Dec 12, 2023
1 parent c843485 commit f2002a1
Show file tree
Hide file tree
Showing 43 changed files with 628 additions and 579 deletions.
5 changes: 5 additions & 0 deletions .wiki/Configuration-Policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ Role can also be deactivated (it means that role does not grant any permissions

Individual user permissions and role permissions are simply joined for the user.

### Anonymous user
In the case, when a request doesn't contain a user's credentials, all operations are performed on behalf
of the user with the name `anonymous`. You can define permissions and roles that available
to `anonymous` the same way as it's done for regular users.

### Permissions

Permissions in Artipie are based on `java.security.Permission` and `java.security.PermissionCollection`
Expand Down
59 changes: 10 additions & 49 deletions artipie-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -150,56 +150,17 @@
</dependencies>
<build>
<plugins>
<!--<plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.1.2</version>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.8.1</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.7.1</version>
</dependency>
</dependencies>
<executions>
&lt;!&ndash; FIXME: reactive TCK tests are not executed for multipart processor &ndash;&gt;
&lt;!&ndash; <execution> &ndash;&gt;
&lt;!&ndash; <id>surefire-testng</id> &ndash;&gt;
&lt;!&ndash; <phase>test</phase> &ndash;&gt;
&lt;!&ndash; <goals> &ndash;&gt;
&lt;!&ndash; <goal>test</goal> &ndash;&gt;
&lt;!&ndash; </goals> &ndash;&gt;
&lt;!&ndash; <configuration> &ndash;&gt;
&lt;!&ndash; <skip>false</skip> &ndash;&gt;
&lt;!&ndash; <includes> &ndash;&gt;
&lt;!&ndash; <include>com.artipie.http.rq.multipart.MultipartTckTest</include> &ndash;&gt;
&lt;!&ndash; <include>com.artipie.http.rq.multipart.MultipartsTckTest</include> &ndash;&gt;
&lt;!&ndash; </includes> &ndash;&gt;
&lt;!&ndash; <junitArtifactName>none:none</junitArtifactName> &ndash;&gt;
&lt;!&ndash; </configuration> &ndash;&gt;
&lt;!&ndash; </execution> &ndash;&gt;
&lt;!&ndash;<execution>
<id>surefire-junit</id>
<phase>test</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<skip>false</skip>
<excludes>
<exclude>com.artipie.http.rq.multipart.MultipartTckTest</exclude>
<exclude>com.artipie.http.rq.multipart.MultipartsTckTest</exclude>
</excludes>
<testNGArtifactName>none:none</testNGArtifactName>
</configuration>
</execution>&ndash;&gt;
</executions>
</plugin>-->
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>21</release>
<testExcludes>
<exclude>**/com/artipie/http/servlet/**</exclude>
<exclude>**/com/artipie/http/slice/SliceITCase.java</exclude>
</testExcludes>
</configuration>
</plugin>
</plugins>
</build>

Expand Down
121 changes: 99 additions & 22 deletions artipie-core/src/main/java/com/artipie/http/auth/AuthScheme.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package com.artipie.http.auth;

import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
Expand All @@ -13,19 +14,21 @@
* Authentication scheme such as Basic, Bearer etc.
*
* @since 0.17
* @checkstyle JavadocMethodCheck (500 lines)
* @checkstyle JavadocVariableCheck (500 lines)
* @checkstyle AvoidInlineConditionalsCheck (500 lines)
* @checkstyle OperatorWrapCheck (500 lines)
* @checkstyle StringLiteralsConcatenationCheck (500 lines)
*/
@SuppressWarnings({"PMD.ProhibitPublicStaticMethods",
"PMD.ConstructorOnlyInitializesOrCallOtherConstructors"})
public interface AuthScheme {

/**
* Absent auth scheme that authenticates any request as "anonymous" user.
*/
AuthScheme NONE = (hdrs, line) -> CompletableFuture.completedFuture(
new Result() {
@Override
public Optional<AuthUser> user() {
return Optional.of(new AuthUser("anonymous", "unknown"));
}

new Result(AuthStatus.NO_CREDENTIALS, AuthUser.ANONYMOUS, null) {
@Override
public String challenge() {
throw new UnsupportedOperationException();
Expand All @@ -34,7 +37,7 @@ public String challenge() {
);

/**
* Authenticate HTTP request by it's headers and request line.
* Authenticate HTTP request by its headers and request line.
*
* @param headers Request headers.
* @param line Request line.
Expand All @@ -43,7 +46,7 @@ public String challenge() {
CompletionStage<Result> authenticate(Iterable<Map.Entry<String, String>> headers, String line);

/**
* Authenticate HTTP request by it's headers.
* Authenticate HTTP request by its headers.
*
* @param headers Request headers.
* @return Authentication result.
Expand All @@ -52,26 +55,110 @@ default CompletionStage<Result> authenticate(Iterable<Map.Entry<String, String>>
return this.authenticate(headers, "");
}

/**
* Authentication status.
*/
enum AuthStatus {
/**
* Successful authentication.
*/
AUTHENTICATED,
/**
* Failed authentication.
*/
FAILED,
/**
* A request doesn't contain credentials.
*/
NO_CREDENTIALS
}

/**
* Build authentication result.
*
* @param user Result's user
* @param challenge Challenge value
* @return Result
*/
static Result result(final AuthUser user, final String challenge) {
Objects.requireNonNull(user, "User must not be null!");
final AuthStatus status = user.isAnonymous()
? AuthStatus.NO_CREDENTIALS
: AuthStatus.AUTHENTICATED;
return new Result(status, user, challenge);
}

/**
* Build authentication result.
*
* @param user Result's user
* @param challenge Challenge value
* @return Result
*/
static Result result(final Optional<AuthUser> user, final String challenge) {
return user
.map(
authUser -> {
final AuthStatus status = authUser.isAnonymous()
? AuthStatus.NO_CREDENTIALS
: AuthStatus.AUTHENTICATED;
return new Result(status, authUser, challenge);
}).orElseGet(() -> new Result(AuthStatus.FAILED, null, challenge));
}

/**
* HTTP request authentication result.
*
* @since 0.17
*/
interface Result {
class Result {

private final AuthStatus status;

private final AuthUser user;

private final String challenge;

private Result(final AuthStatus status, final AuthUser user, final String challenge) {
assert (status != AuthStatus.FAILED) == (user != null);
this.status = status;
this.user = user;
this.challenge = challenge;
}

/**
* Gets authentication status.
*
* @return AuthenticationStatus.
*/
public AuthStatus status() {
return this.status;
}

/**
* Authenticated user.
*
* @return Authenticated user, empty if not authenticated.
*/
Optional<AuthUser> user();
public AuthUser user() {
return this.user;
}

/**
* Get authentication challenge that is provided in response WWW-Authenticate header value.
*
* @return Authentication challenge for client.
*/
String challenge();
public String challenge() {
return this.challenge;
}

@Override
public String toString() {
final String usr = this.status == AuthStatus.FAILED ? "" :
", user=" + this.user.name();
return String.format("Result: [status=%s%s]", this.status, usr);
}
}

/**
Expand Down Expand Up @@ -126,17 +213,7 @@ public CompletionStage<Result> authenticate(
final String line
) {
return CompletableFuture.completedFuture(
new Result() {
@Override
public Optional<AuthUser> user() {
return Fake.this.usr;
}

@Override
public String challenge() {
return Fake.this.chllng;
}
}
AuthScheme.result(this.usr, this.chllng)
);
}
}
Expand Down
18 changes: 16 additions & 2 deletions artipie-core/src/main/java/com/artipie/http/auth/AuthUser.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
*/
public final class AuthUser {

/**
* Anonymous user is a user who doesn't have any credentials.
*/
public static final AuthUser ANONYMOUS = new AuthUser("anonymous", "unknown");

/**
* User name.
*/
Expand All @@ -30,7 +35,7 @@ public final class AuthUser {
* @param context Authentication context
*/
public AuthUser(final String name, final String context) {
this.uname = name;
this.uname = Objects.requireNonNull(name, "User's name cannot be null!");
this.context = context;
}

Expand All @@ -44,7 +49,7 @@ public AuthUser(final String name, final String context) {
}

/**
* Ger user name.
* Get user's name.
*
* @return Name
*/
Expand All @@ -60,6 +65,15 @@ public String authContext() {
return this.context;
}

/**
* This user is anonymous.
*
* @return True if user is anonymous.
*/
public boolean isAnonymous() {
return this.uname.equals(AuthUser.ANONYMOUS.uname);
}

@Override
public boolean equals(final Object other) {
final boolean res;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,7 @@ public interface Authentication {
/**
* Resolve anyone as an anonymous user.
*/
Authentication ANONYMOUS = (name, pswd) -> Optional.of(new AuthUser("anonymous", "unknown"));

/**
* Any user instance.
*/
AuthUser ANY_USER = new AuthUser("*", "any");
Authentication ANONYMOUS = (name, pswd) -> Optional.of(AuthUser.ANONYMOUS);

/**
* Find user by credentials.
Expand Down
Loading

0 comments on commit f2002a1

Please sign in to comment.