mirror of
https://github.com/paralus/paralus.git
synced 2026-08-24 15:47:19 +00:00
record user.login event by kratos hooks (#111)
* record user.login event by kratos hooks Signed-off-by: mabhi <abhijit.mukherjee@infracloud.io> * added test case for create login auditlog Signed-off-by: mabhi <abhijit.mukherjee@infracloud.io> * updated change log Signed-off-by: mabhi <abhijit.mukherjee@infracloud.io>
This commit is contained in:
@@ -112,6 +112,25 @@ func CreateUserAuditEvent(ctx context.Context, al *zap.Logger, db bun.IDB, actio
|
||||
}
|
||||
}
|
||||
|
||||
func CreateUserLoginAuditEvent(ctx context.Context, al *zap.Logger, action string, name string) {
|
||||
sd, ok := GetSessionDataFromContext(ctx)
|
||||
if !ok {
|
||||
_log.Warn("unable to create audit event: could not fetch info from context")
|
||||
return
|
||||
}
|
||||
|
||||
detail := &audit.EventDetail{
|
||||
Message: fmt.Sprintf("User login: %s", name),
|
||||
Meta: map[string]string{
|
||||
"user": name,
|
||||
},
|
||||
}
|
||||
if err := audit.CreateV1Event(al, sd, detail, fmt.Sprintf("user.%s.success", action), ""); err != nil {
|
||||
_log.Warn("unable to create audit event", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func CreateGroupAuditEvent(ctx context.Context, al *zap.Logger, db bun.IDB, action string, name string, id uuid.UUID, usersBefore, usersAfter, rolesBefore, rolesAfter []uuid.UUID) {
|
||||
sd, ok := GetSessionDataFromContext(ctx)
|
||||
if !ok {
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/paralus/paralus/pkg/utils"
|
||||
userrpcv3 "github.com/paralus/paralus/proto/rpc/user"
|
||||
authzv1 "github.com/paralus/paralus/proto/types/authz"
|
||||
commonv3 "github.com/paralus/paralus/proto/types/commonpb/v3"
|
||||
v3 "github.com/paralus/paralus/proto/types/commonpb/v3"
|
||||
userv3 "github.com/paralus/paralus/proto/types/userpb/v3"
|
||||
)
|
||||
@@ -52,6 +53,8 @@ type UserService interface {
|
||||
UpdateIdpUserGroupPolicy(context.Context, string, string, string) error
|
||||
// Generate recovery link for users
|
||||
ForgotPassword(context.Context, *userrpcv3.UserForgotPasswordRequest) (*userrpcv3.UserForgotPasswordResponse, error)
|
||||
// Generate auditLog event
|
||||
CreateLoginAuditLog(context.Context, *userrpcv3.UserLoginAuditRequest) (*userrpcv3.UserLoginAuditResponse, error)
|
||||
}
|
||||
|
||||
type userService struct {
|
||||
@@ -1072,6 +1075,26 @@ func (s *userService) ForgotPassword(ctx context.Context, req *userrpcv3.UserFor
|
||||
}
|
||||
}
|
||||
|
||||
func (s *userService) CreateLoginAuditLog(ctx context.Context, req *userrpcv3.UserLoginAuditRequest) (*userrpcv3.UserLoginAuditResponse, error) {
|
||||
uid, err := uuid.Parse(req.UserId)
|
||||
if err != nil {
|
||||
return &userrpcv3.UserLoginAuditResponse{}, fmt.Errorf("unable to create login audit event. reason: uid parse error.%v", err.Error())
|
||||
}
|
||||
|
||||
entities, err := dao.GetUserNamesByIds(ctx, s.db, []uuid.UUID{uid}, &models.KratosIdentities{})
|
||||
if err != nil {
|
||||
return &userrpcv3.UserLoginAuditResponse{}, fmt.Errorf("unable to create login audit event. reason: internal error. %v", err.Error())
|
||||
}
|
||||
if len(entities) == 0 {
|
||||
return &userrpcv3.UserLoginAuditResponse{}, fmt.Errorf("unable to create login audit event. reason: user not found")
|
||||
}
|
||||
username := entities[0]
|
||||
new_ctx := context.WithValue(ctx, common.SessionDataKey, &commonv3.SessionData{Username: username})
|
||||
CreateUserLoginAuditEvent(new_ctx, s.al, "login", username)
|
||||
|
||||
return &userrpcv3.UserLoginAuditResponse{}, nil
|
||||
}
|
||||
|
||||
func (s *userService) getUserLastLogin(ctx context.Context, userId uuid.UUID) (string, error) {
|
||||
var lastLogin string
|
||||
authTime, err := dao.GetUserLastAuthTime(ctx, s.db, userId)
|
||||
|
||||
@@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -854,3 +855,47 @@ func TestUserRetrieveCliConfigCreate(t *testing.T) {
|
||||
t.Error("invalid partner name")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateLoginAuditLog(t *testing.T) {
|
||||
tt := []struct {
|
||||
name string
|
||||
uuid string
|
||||
invalid bool
|
||||
shouldHaveError bool
|
||||
}{
|
||||
{"invalid uid format", "user-" + uuid.New().String(), false, true},
|
||||
{"invalid user id", uuid.New().String(), true, true},
|
||||
{"valid user id", uuid.New().String(), false, false},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
db, mock := getDB(t)
|
||||
defer db.Close()
|
||||
|
||||
ap := &mockAuthProvider{}
|
||||
mazc := mockAuthzClient{}
|
||||
us := NewUserService(ap, db, &mazc, nil, common.CliConfigDownloadData{}, getLogger(), true)
|
||||
if tc.invalid {
|
||||
|
||||
uid := uuid.New().String()
|
||||
// without regexp QuoteMeta, getting mismatch actual and required SQL queries
|
||||
mock.ExpectQuery(regexp.QuoteMeta(`SELECT traits ->> 'email' as name FROM "identities" WHERE (id = ('` + uid + `'))`)).
|
||||
WithArgs().WillReturnRows(sqlmock.NewRows([]string{"traits"}).AddRow([]byte(`{"email":"johndoe@provider.com"}`)))
|
||||
|
||||
} else {
|
||||
mock.ExpectQuery(regexp.QuoteMeta(`SELECT traits ->> 'email' as name FROM "identities" WHERE (id = ('` + tc.uuid + `'))`)).
|
||||
WithArgs().WillReturnRows(sqlmock.NewRows([]string{"traits"}).AddRow([]byte(`{"email":"johndoe@provider.com"}`)))
|
||||
|
||||
}
|
||||
|
||||
audreq := &userrpcv3.UserLoginAuditRequest{UserId: tc.uuid}
|
||||
_, err := us.CreateLoginAuditLog(context.TODO(), audreq)
|
||||
if tc.shouldHaveError && err == nil {
|
||||
|
||||
t.Error("could not add audit log", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user