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:
Abhijit Mukherjee
2022-12-26 11:55:19 +05:30
committed by GitHub
parent f5cbe6ca1e
commit cd55d2d1da
11 changed files with 934 additions and 369 deletions
+19
View File
@@ -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 {
+23
View File
@@ -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)
+45
View File
@@ -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)
}
})
}
}