Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove equals & hashcode based on JWT, KafkaPrincipal ones will be in effect #60

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions oauth-keycloak-authorizer/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@
<artifactId>scala-library</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-annotations</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand All @@ -56,6 +67,10 @@
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven.surefire.version}</version>
</plugin>
</plugins>
</build>
</project>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,18 @@
import io.strimzi.kafka.oauth.common.BearerTokenWithPayload;
import org.apache.kafka.common.security.auth.KafkaPrincipal;

import java.util.Objects;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
* This class uses the KafkaPrincipal object to store additional info obtained at sesion authentication time,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* This class uses the KafkaPrincipal object to store additional info obtained at sesion authentication time,
* This class uses the KafkaPrincipal object to store additional info obtained at session authentication time,

* and required later by a custom authorizer.
*
* This class is the only notion of client session that we can get. Kafka code holds on to it for as long as the session is alive,
* and then the object can be garbage collected.
*
* Any additional fields should not be included in equals / hashcode check. If they are, that will break re-authentication.
*/
@SuppressFBWarnings("EQ_DOESNT_OVERRIDE_EQUALS")
public class JwtKafkaPrincipal extends KafkaPrincipal {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't ever expect this class to be subclassed, but I guess if it were the subclass could override equals()/hashCode(), breaking this contract. So I suppose we should either make this class final, or do it as originally with overridden (but final) equals()/hashCode() delegating to super.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like an overkill to me, but sure, we can make the class final.
Not that it should be treated like a contract with third party devs that might get an impression that it's a good idea to extend our classes, and think that we won't one day completely change them. Everything in terms of classes, methods, and configuration that we don't explicitly document should be treated as an implementation detail that can change at any time.
This final should only be considered a failsafe for a contributor to this project, to save them from inadvertently re-introducing the bug.


private final BearerTokenWithPayload jwt;
Expand All @@ -25,20 +35,4 @@ public JwtKafkaPrincipal(String principalType, String name, BearerTokenWithPaylo
public BearerTokenWithPayload getJwt() {
return jwt;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

if (!super.equals(o)) return false;

JwtKafkaPrincipal that = (JwtKafkaPrincipal) o;
return Objects.equals(jwt, that.jwt);
}

@Override
public int hashCode() {
return Objects.hash(super.hashCode(), jwt);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright 2017-2019, Strimzi authors.
* License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
*/
package io.strimzi.kafka.oauth.server.authorizer;

import io.strimzi.kafka.oauth.common.BearerTokenWithPayload;
import org.junit.Assert;
import org.junit.Test;

public class JwtKafkaPrincipalTest {

@Test
public void testEquals() {

BearerTokenWithPayload token = new MockBearerTokenWithPayload("service-account-my-client",
System.currentTimeMillis(), System.currentTimeMillis() + 60000, null, "BEARER-TOKEN-9823eh982u", "Whatever");
JwtKafkaPrincipal principal = new JwtKafkaPrincipal("User", "service-account-my-client", token);


BearerTokenWithPayload token2 = new MockBearerTokenWithPayload("bob",
System.currentTimeMillis(), System.currentTimeMillis() + 60000, null, "BEARER-TOKEN-0000dd0000", null);
JwtKafkaPrincipal principal2 = new JwtKafkaPrincipal("User", "service-account-my-client", token2);


JwtKafkaPrincipal principal3 = new JwtKafkaPrincipal("User", "service-account-my-client");

JwtKafkaPrincipal principal4 = new JwtKafkaPrincipal("User", "bob");


Assert.assertTrue("principal should be equal to principal2", principal.equals(principal2));
Assert.assertTrue("principal2 should be equal to principal", principal2.equals(principal));

Assert.assertTrue("principal should be equal to principal3", principal.equals(principal3));
Assert.assertTrue("principal3 should be equal to principal", principal3.equals(principal));

Assert.assertTrue("principal2 should be equal to principal3", principal2.equals(principal3));
Assert.assertTrue("principal3 should be equal to principal2", principal3.equals(principal2));

Assert.assertTrue("principal should be equal to itself", principal.equals(principal));
Assert.assertTrue("principal2 should be equal to itself", principal2.equals(principal2));
Assert.assertTrue("principal3 should be equal to itself", principal3.equals(principal3));
Assert.assertTrue("principal4 should be equal to itself", principal4.equals(principal4));

Assert.assertFalse("principal should not be equal to principal4", principal.equals(principal4));
Assert.assertFalse("principal4 should not be equal to principal", principal4.equals(principal));
Assert.assertFalse("principal3 should not be equal to principal4", principal3.equals(principal4));
Assert.assertFalse("principal4 should not be equal to principal3", principal4.equals(principal3));

long hash1 = principal.hashCode();
long hash2 = principal2.hashCode();
long hash3 = principal3.hashCode();
long hash4 = principal4.hashCode();

Assert.assertTrue("Hashcode1 should be equal to hashcode2", hash1 == hash2);
Assert.assertTrue("Hashcode1 should be equal to hashcode3", hash1 == hash3);
Assert.assertFalse("Hashcode1 should not be equal to hashcode4", hash1 == hash4);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright 2017-2019, Strimzi authors.
* License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
*/
package io.strimzi.kafka.oauth.server.authorizer;

import io.strimzi.kafka.oauth.common.BearerTokenWithPayload;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

public class MockBearerTokenWithPayload implements BearerTokenWithPayload {


private final String principalName;
private final long createTime;
private final long lifetime;
private final Set<String> scopes;
private final String token;
private Object payload;

MockBearerTokenWithPayload(String principalName, long createTime, long lifetime, String scope, String token, Object payload) {
this.principalName = principalName;
this.createTime = createTime;
this.lifetime = lifetime;

Set<String> scopesSet = new HashSet<>();
String[] parsedScopes = scope != null ? scope.split(" ") : new String[0];
for (String s: parsedScopes) {
scopesSet.add(s);
}
scopes = Collections.unmodifiableSet(scopesSet);

this.token = token;
this.payload = payload;
}

@Override
public Object getPayload() {
return payload;
}

@Override
public void setPayload(Object payload) {
this.payload = payload;
}

@Override
public String value() {
return token;
}

@Override
public Set<String> scope() {
return scopes;
}

@Override
public long lifetimeMs() {
return lifetime;
}

@Override
public String principalName() {
return principalName;
}

@Override
public Long startTimeMs() {
return createTime;
}
}
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.version>3.8.1</maven.compiler.version>
<maven.dependency.version>3.1.1</maven.dependency.version>
<maven.surefire.version>2.22.1</maven.surefire.version>
<maven.javadoc.version>3.1.0</maven.javadoc.version>
<maven.source.version>3.0.1</maven.source.version>
<maven.gpg.version>1.6</maven.gpg.version>
Expand Down Expand Up @@ -210,6 +211,10 @@
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven.surefire.version}</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
Expand Down