Repository: Nerzal/gocloak
Pull request #290 (2021-06-29 07:05:19)

Consent revocation endpoint
In our project, we need to use [consent revocation endpoint](https://www.keycloak.org/docs-api/5.0/rest-api/index.html#_revokeconsent) and we wanted to give this library a try :) Since it was missing I added it for the client. If there is any issue/suggestion with the change, please let me know!


Patch:
diff --git a/README.md b/README.md
index 63c5c36..adb8b3e 100644
--- a/README.md
+++ b/README.md
@@ -176,6 +176,7 @@ type GoCloak interface {
  Logout(ctx context.Context, clientID, clientSecret, realm, refreshToken string) error
  LogoutPublicClient(ctx context.Context, clientID, realm, accessToken, refreshToken string) error
  LogoutAllSessions(ctx context.Context, accessToken, realm, userID string) error
+ RevokeUserConsents(ctx context.Context, accessToken, realm, userID, clientID string) error
  LogoutUserSession(ctx context.Context, accessToken, realm, session string) error
  LoginClient(ctx context.Context, clientID, clientSecret, realm string) (*JWT, error)
  LoginClientSignedJWT(ctx context.Context, clientID, realm string, key interface{}, signedMethod jwt.SigningMethod, expiresAt *jwt.Time) (*JWT, error)
diff --git a/client.go b/client.go
index 08f92d0..5463a7a 100644
--- a/client.go
+++ b/client.go
@@ -622,6 +622,15 @@ func (client *gocloak) LogoutAllSessions(ctx context.Context, accessToken, realm
 	return checkForError(resp, err, errMessage)
 }
 
+func (client *gocloak) RevokeUserConsents(ctx context.Context, accessToken, realm, userID, clientID string) error {
+	const errMessage = "could not revoke consents"
+
+	resp, err := client.getRequestWithBearerAuth(ctx, accessToken).
+		Delete(client.getAdminRealmURL(realm, "users", userID, "consents", clientID))
+
+	return checkForError(resp, err, errMessage)
+}
+
 // LogoutUserSessions logs out a single sessions of a user given a session id
 func (client *gocloak) LogoutUserSession(ctx context.Context, accessToken, realm, session string) error {
 	const errMessage = "could not logout"
diff --git a/gocloak.go b/gocloak.go
index 8f0d223..5ea36cb 100644
--- a/gocloak.go
+++ b/gocloak.go
@@ -33,6 +33,8 @@ type GoCloak interface {
 	LogoutPublicClient(ctx context.Context, idOfClient, realm, accessToken, refreshToken string) error
 	// LogoutAllSessions logs out all sessions of a user given an id
 	LogoutAllSessions(ctx context.Context, accessToken, realm, userID string) error
+	// RevokeConsents revoke consent and offline tokens for particular client from user
+	RevokeUserConsents(ctx context.Context, accessToken, realm, userID, clientID string) error
 	// LogoutUserSessions logs out a single sessions of a user given a session id.
 	// NOTE: this uses bearer token, but this token must belong to a user with proper privileges
 	LogoutUserSession(ctx context.Context, accessToken, realm, session string) error


Test patch:
diff --git a/client_test.go b/client_test.go
index e790398..5579355 100644
--- a/client_test.go
+++ b/client_test.go
@@ -2091,6 +2091,38 @@ func TestGocloak_LogoutAllSessions(t *testing.T) {
 	require.NoError(t, err, "Logout failed")
 }
 
+func TestGocloak_RevokeUserConsents(t *testing.T) {
+	// t.Parallel()
+	cfg := GetConfig(t)
+	client := NewClientWithDebug(t)
+	SetUpTestUser(t, client)
+	_, err := client.GetToken(
+		context.Background(),
+		cfg.GoCloak.Realm,
+		gocloak.TokenOptions{
+			ClientID:      &cfg.GoCloak.ClientID,
+			ClientSecret:  &cfg.GoCloak.ClientSecret,
+			Username:      &cfg.GoCloak.UserName,
+			Password:      &cfg.GoCloak.Password,
+			GrantType:     gocloak.StringP("password"),
+			ResponseTypes: &[]string{"token", "id_token"},
+			Scopes:        &[]string{"openid", "offline_access"},
+		},
+	)
+	require.NoError(t, err, "Login failed")
+	token := GetAdminToken(t, client)
+
+	err = client.RevokeUserConsents(
+		context.Background(),
+		token.AccessToken,
+		cfg.GoCloak.Realm,
+		testUserID,
+		cfg.GoCloak.ClientID,
+	)
+
+	require.NoError(t, err, "Consent revocation failed")
+}
+
 func TestGocloak_LogoutUserSession(t *testing.T) {
 	// t.Parallel()
 	cfg := GetConfig(t)