Skip to content

Commit

Permalink
Merge branch 'Nerzal:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
upils authored Jan 9, 2023
2 parents 64d1cd1 + 94bb940 commit ec92883
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 0 deletions.
24 changes: 24 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type GoCloak struct {
tokenEndpoint string
logoutEndpoint string
openIDConnect string
attackDetection string
}
}

Expand Down Expand Up @@ -185,6 +186,7 @@ func NewClient(basePath string, options ...func(*GoCloak)) *GoCloak {
c.Config.tokenEndpoint = makeURL("protocol", "openid-connect", "token")
c.Config.logoutEndpoint = makeURL("protocol", "openid-connect", "logout")
c.Config.openIDConnect = makeURL("protocol", "openid-connect")
c.Config.attackDetection = makeURL("attack-detection", "brute-force")

for _, option := range options {
option(&c)
Expand Down Expand Up @@ -214,6 +216,11 @@ func (g *GoCloak) getAdminRealmURL(realm string, path ...string) string {
return makeURL(path...)
}

func (g *GoCloak) getAttackDetectionURL(realm string, user string, path ...string) string {
path = append([]string{g.basePath, g.Config.authAdminRealms, realm, g.Config.attackDetection, user}, path...)
return makeURL(path...)
}

// ==== Functional Options ===

// SetLegacyWildFlySupport maintain legacy WildFly support.
Expand Down Expand Up @@ -593,6 +600,7 @@ func (g *GoCloak) Login(ctx context.Context, clientID, clientSecret, realm, user
GrantType: StringP("password"),
Username: &username,
Password: &password,
Scope: StringP("openid"),
})
}

Expand Down Expand Up @@ -2787,6 +2795,22 @@ func (g *GoCloak) DeleteUserFederatedIdentity(ctx context.Context, token, realm,
return checkForError(resp, err, errMessage)
}

// GetUserBruteForceDetectionStatus fetches a user status regarding brute force protection
func (g *GoCloak) GetUserBruteForceDetectionStatus(ctx context.Context, accessToken, realm, userID string) (*BruteForceStatus, error) {
const errMessage = "could not brute force detection Status"
var result BruteForceStatus

resp, err := g.getRequestWithBearerAuth(ctx, accessToken).
SetResult(&result).
Get(g.getAttackDetectionURL(realm, "users", userID))

if err := checkForError(resp, err, errMessage); err != nil {
return nil, err
}

return &result, nil
}

// ------------------
// Identity Providers
// ------------------
Expand Down
81 changes: 81 additions & 0 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3055,6 +3055,87 @@ func Test_CreateUser(t *testing.T) {
defer tearDown()
}

func Test_GetUserBruteForceDetectionStatus(t *testing.T) {
cfg := GetConfig(t)
client := NewClientWithDebug(t)
token := GetAdminToken(t, client)
realm, err := client.GetRealm(
context.Background(),
token.AccessToken,
cfg.GoCloak.Realm)
require.NoError(t, err, "GetRealm failed")

updatedRealm := realm
updatedRealm.BruteForceProtected = gocloak.BoolP(true)
updatedRealm.FailureFactor = gocloak.IntP(1)
updatedRealm.MaxFailureWaitSeconds = gocloak.IntP(2)
err = client.UpdateRealm(
context.Background(),
token.AccessToken,
*updatedRealm)
require.NoError(t, err, "UpdateRealm failed")

tearDownUser, userID := CreateUser(t, client)
defer tearDownUser()
err = client.SetPassword(
context.Background(),
token.AccessToken,
userID,
*realm.ID,
cfg.GoCloak.Password,
false)
require.NoError(t, err, "CreateUser failed")

fetchedUser, err := client.GetUserByID(
context.Background(),
token.AccessToken,
cfg.GoCloak.Realm,
userID)
require.NoError(t, err, "GetUserById failed")

_, err = client.Login(context.Background(),
cfg.GoCloak.ClientID,
cfg.GoCloak.ClientSecret,
*realm.ID,
*fetchedUser.Username,
"wrong password")
require.Error(t, err, "401 Unauthorized: invalid_grant: Invalid user credentials")
bruteForceStatus, err := client.GetUserBruteForceDetectionStatus(
context.Background(),
token.AccessToken,
cfg.GoCloak.Realm,
userID)
require.NoError(t, err, "Getting attack log failed")
require.Equal(t, 1, *bruteForceStatus.NumFailures, "Should return one failure")
require.Equal(t, true, *bruteForceStatus.Disabled, "The user shouldn be locked")

time.Sleep(2 * time.Second)
_, err = client.Login(
context.Background(),
cfg.GoCloak.ClientID,
cfg.GoCloak.ClientSecret,
*realm.ID,
*fetchedUser.Username,
cfg.GoCloak.Password)
require.NoError(t, err, "Login failed")

bruteForceStatus, err = client.GetUserBruteForceDetectionStatus(
context.Background(),
token.AccessToken,
cfg.GoCloak.Realm,
userID)
require.NoError(t, err, "Getting attack status failed")
require.Equal(t, 0, *bruteForceStatus.NumFailures, "Should return zero failures")
require.Equal(t, false, *bruteForceStatus.Disabled, "The user shouldn't be locked")

err = client.UpdateRealm(
context.Background(),
token.AccessToken,
*realm)
require.NoError(t, err, "UpdateRealm failed")

}

func Test_CreateUserCustomAttributes(t *testing.T) {
t.Parallel()
cfg := GetConfig(t)
Expand Down
9 changes: 9 additions & 0 deletions models.go
Original file line number Diff line number Diff line change
Expand Up @@ -1360,6 +1360,14 @@ type CredentialRepresentation struct {
UserLabel *string `json:"userLabel,omitempty"`
}

// BruteForceStatus is a representation of realm user regarding brute force attack
type BruteForceStatus struct {
NumFailures *int `json:"numFailures,omitempty"`
Disabled *bool `json:"disabled,omitempty"`
LastIPFailure *string `json:"lastIPFailure,omitempty"`
LastFailure *int `json:"lastFailure,omitempty"`
}

// RequiredActionProviderRepresentation is a representation of required actions
// v15: https://www.keycloak.org/docs-api/15.0/rest-api/index.html#_requiredactionproviderrepresentation
type RequiredActionProviderRepresentation struct {
Expand Down Expand Up @@ -1464,3 +1472,4 @@ func (v *ResourcePolicyRepresentation) String() string { return pre
func (v *GetResourcePoliciesParams) String() string { return prettyStringStruct(v) }
func (v *CredentialRepresentation) String() string { return prettyStringStruct(v) }
func (v *RequiredActionProviderRepresentation) String() string { return prettyStringStruct(v) }
func (v *BruteForceStatus) String() string { return prettyStringStruct(v) }

0 comments on commit ec92883

Please sign in to comment.