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

Back office events #24

Merged
merged 16 commits into from
Apr 16, 2019
Merged
Show file tree
Hide file tree
Changes from all 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
236 changes: 116 additions & 120 deletions Gopkg.lock

Large diffs are not rendered by default.

21 changes: 18 additions & 3 deletions cmd/keycloakb/keycloak_bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,9 +334,19 @@ func main() {
{
var managementLogger = log.With(logger, "svc", "management")

// module to store API calls of the back office to the DB
var eventsDBModule event.EventsDBModule
{
eventsDBModule = event.NewEventsDBModule(eventsDBConn)
eventsDBModule = event.MakeEventsDBModuleInstrumentingMW(influxMetrics.NewHistogram("eventsDB_module"))(eventsDBModule)
eventsDBModule = event.MakeEventsDBModuleLoggingMW(log.With(managementLogger, "mw", "module", "unit", "eventsDB"))(eventsDBModule)
eventsDBModule = event.MakeEventsDBModuleTracingMW(tracer)(eventsDBModule)

}

var keycloakComponent management.Component
{
keycloakComponent = management.NewComponent(keycloakClient)
keycloakComponent = management.NewComponent(keycloakClient, eventsDBModule)
}

var getRealmEndpoint endpoint.Endpoint
Expand Down Expand Up @@ -727,13 +737,18 @@ func config(logger log.Logger) *viper.Viper {
v.SetDefault("keycloak-timeout", "5s")

//Storage events in DB
v.SetDefault("events-DB", false)
v.SetDefault("events-db", false)

// DB
v.SetDefault("db-host-port", "")
v.SetDefault("db-username", "")
v.SetDefault("db-password", "")
v.SetDefault("db-database", "")
v.SetDefault("db-table", "")
v.SetDefault("protocol", "")
v.SetDefault("db-protocol", "")
v.SetDefault("db-max-open-conns", 10)
v.SetDefault("db-max-idle-conns", 2)
v.SetDefault("db-conn-max-lifetime", 3600)

// Rate limiting (in requests/second)
v.SetDefault("rate-event", 1000)
Expand Down
124 changes: 74 additions & 50 deletions pkg/event/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@ import (
"context"
"encoding/json"
"fmt"
"regexp"
"strings"
"sync"
"time"

"github.com/cloudtrust/keycloak-bridge/api/event/fb"
)

const (
timeFormat = "2006-01-02 15:04:05.000"
)

// MuxComponent is the Mux component interface.
type MuxComponent interface {
Event(ctx context.Context, eventType string, obj []byte) error
Expand Down Expand Up @@ -122,48 +127,44 @@ func (c *adminComponent) AdminEvent(ctx context.Context, adminEvent *fb.AdminEve
func addCTtypeToEvent(event map[string]string) map[string]string {
// add the ct_event_type

switch opType := event["operationType"]; opType {
addInfo := []byte(event["additional_info"])
var f map[string]string
_ = json.Unmarshal(addInfo, &f)

switch opType := event["kc_operation_type"]; opType {
case "CREATE":
//ACCOUNT_CREATED
// check if the resourcePath starts with prefix users
if strings.HasPrefix(event["resourcePath"], "users") {
if strings.HasPrefix(f["resource_path"], "users") {
event["ct_event_type"] = "ACCOUNT_CREATED"
return event
}
case "ACTION":
//ACTIVATION_EMAIL_SENT
// check if the resourcePath ends with sufix send-verify-email
if strings.HasSuffix(event["resourcePath"], "send-verify-email") {
// check if the resourcePath ends with suffix send-verify-email
if strings.HasSuffix(f["resource_path"], "send-verify-email") {
event["ct_event_type"] = "ACTIVATION_EMAIL_SENT"
return event
}
default:

// Nothing to do here
}

switch t := event["type"]; t {
switch t := event["kc_event_type"]; t {
case "CUSTOM_REQUIRED_ACTION":
//EMAIL_CONFIRMED
eventDetails := []byte(event["details"])
var f map[string]string
_ = json.Unmarshal(eventDetails, &f)

if f["custom_required_action"] == "VERIFY_EMAIL" {
event["ct_event_type"] = "EMAIL_CONFIRMED"
return event
}
case "EXECUTE_ACTION_TOKEN_ERROR":
//CONFIRM_EMAIL_EXPIRED
if event["error"] == "expired_code" {
if f["error"] == "expired_code" {
event["ct_event_type"] = "CONFIRM_EMAIL_EXPIRED"
return event
}
case "UPDATE_PASSWORD":
//PASSWORD_RESET
eventDetails := []byte(event["details"])
var f map[string]string
_ = json.Unmarshal(eventDetails, &f)

if f["custom_required_action"] == "sms-password-set" {
event["ct_event_type"] = "PASSWORD_RESET"
return event
Expand All @@ -182,7 +183,7 @@ func addCTtypeToEvent(event map[string]string) map[string]string {
event["ct_event_type"] = "LOGOUT"
return event
default:

// Nothing to do here
}

// for all those events that don't have set the ct_event_type, we assign an empty ct_event_type
Expand All @@ -195,33 +196,40 @@ func addCTtypeToEvent(event map[string]string) map[string]string {

func adminEventToMap(adminEvent *fb.AdminEvent) map[string]string {
var adminEventMap = make(map[string]string)
adminEventMap["uid"] = fmt.Sprint(adminEvent.Uid())
var addInfo = make(map[string]string)

time := epochMilliToTime(adminEvent.Time())
adminEventMap["time"] = time.Format("2006-01-02T15:04:05.000Z")
addInfo["uid"] = fmt.Sprint(adminEvent.Uid())

adminEventMap["realmId"] = string(adminEvent.RealmId())
time := epochMilliToTime(adminEvent.Time()).UTC()
adminEventMap["audit_time"] = time.Format(timeFormat) //audit_time

adminEventMap["realm_name"] = string(adminEvent.RealmId()) //realm_name
adminEventMap["origin"] = "keycloak" //origin

authDetails := adminEvent.AuthDetails(nil)
var authDetailsMap map[string]string
authDetailsMap = make(map[string]string)
authDetailsMap["clientId"] = string(authDetails.ClientId())
authDetailsMap["ipAddress"] = string(authDetails.IpAddress())
authDetailsMap["realmId"] = string(authDetails.RealmId())
authDetailsMap["userId"] = string(authDetails.UserId())

// BE AWARE: error is not treated
authDetailsJson, _ := json.Marshal(authDetailsMap)
adminEventMap["authDetails"] = string(authDetailsJson)

adminEventMap["resourceType"] = string(adminEvent.ResourceType())
adminEventMap["operationType"] = fb.EnumNamesOperationType[int8(adminEvent.OperationType())]
adminEventMap["resourcePath"] = string(adminEvent.ResourcePath())
adminEventMap["representation"] = string(adminEvent.Representation())
adminEventMap["error"] = string(adminEvent.Error())
adminEventMap["client_id"] = string(authDetails.ClientId()) //client_id
addInfo["ip_address"] = string(authDetails.IpAddress())
adminEventMap["agent_realm_name"] = string(authDetails.RealmId()) // agent_realm_name
adminEventMap["agent_user_id"] = string(authDetails.UserId()) //agent_user_id

addInfo["resource_type"] = string(adminEvent.ResourceType())
adminEventMap["kc_operation_type"] = fb.EnumNamesOperationType[int8(adminEvent.OperationType())] //kc_operation_type
addInfo["resource_path"] = string(adminEvent.ResourcePath())
reg := regexp.MustCompile(`[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}`)
if strings.HasPrefix(addInfo["resource_path"], "users") {
adminEventMap["user_id"] = string(reg.Find([]byte(addInfo["resource_path"]))) //user_id
}

addInfo["representation"] = string(adminEvent.Representation())
addInfo["error"] = string(adminEvent.Error())
//all the admin events have, by default, the ct_event_type set to admin
adminEventMap["ct_event_type"] = "ADMIN"

// BE AWARE: error is not treated
infoJSON, _ := json.Marshal(addInfo)
adminEventMap["additional_info"] = string(infoJSON)

//set the correct ct_event_type for actions like create_account, etc.
adminEventMap = addCTtypeToEvent(adminEventMap)

Expand All @@ -230,36 +238,52 @@ func adminEventToMap(adminEvent *fb.AdminEvent) map[string]string {

func eventToMap(event *fb.Event) map[string]string {
var eventMap = make(map[string]string)
eventMap["uid"] = fmt.Sprint(event.Uid())
var addInfo = make(map[string]string)
// if an event has the ct_event_type set already, the flag avoids rewriting it
var doNotSetCTEventType bool = false

addInfo["uid"] = fmt.Sprint(event.Uid())

time := epochMilliToTime(event.Time())
eventMap["time"] = time.Format("2006-01-02T15:04:05.000Z")
time := epochMilliToTime(event.Time()).UTC()
eventMap["audit_time"] = time.Format(timeFormat) //audit_time

eventMap["type"] = fb.EnumNamesEventType[int8(event.Type())]
eventMap["realmId"] = string(event.RealmId())
eventMap["clientId"] = string(event.ClientId())
eventMap["userId"] = string(event.UserId())
eventMap["sessionId"] = string(event.SessionId())
eventMap["ipAddress"] = string(event.IpAddress())
eventMap["error"] = string(event.Error())
eventMap["kc_event_type"] = fb.EnumNamesEventType[int8(event.Type())] // kc_event_type
eventMap["realm_name"] = string(event.RealmId()) //realm_name
eventMap["client_id"] = string(event.ClientId()) //client_id
eventMap["agent_user_id"] = string(event.UserId()) //agent_user_id
eventMap["user_id"] = string(event.UserId()) //user_id
//Note: we make the assumption that the agent and the user are the same in the case of the events that are not admin events

addInfo["session_id"] = string(event.SessionId())
addInfo["ip_address"] = string(event.IpAddress())
addInfo["error"] = string(event.Error())
eventMap["origin"] = "keycloak" //origin

var detailsMap = make(map[string]string)
var detailsLength = event.DetailsLength()
for i := 0; i < detailsLength; i++ {
var tuple = new(fb.Tuple)
event.Details(tuple, i)
if string(tuple.Key()) == "ct_event_type" {
eventMap[string(tuple.Key())] = string(tuple.Value())
doNotSetCTEventType = true
} else {
detailsMap[string(tuple.Key())] = string(tuple.Value())
if string(tuple.Key()) == "username" {
eventMap["agent_username"] = string(tuple.Value()) //agent_username
eventMap[string(tuple.Key())] = string(tuple.Value()) //username
} else {
addInfo[string(tuple.Key())] = string(tuple.Value())
}

}
}

// BE AWARE: error is not treated
detailsJson, _ := json.Marshal(detailsMap)
eventMap["details"] = string(detailsJson)
infoJSON, _ := json.Marshal(addInfo)
eventMap["additional_info"] = string(infoJSON)

eventMap = addCTtypeToEvent(eventMap)
if !doNotSetCTEventType {
eventMap = addCTtypeToEvent(eventMap)
}

return eventMap
}
Expand Down
82 changes: 62 additions & 20 deletions pkg/event/component_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package event

import (
"context"
"encoding/json"
"errors"
"strconv"
"testing"
Expand Down Expand Up @@ -123,13 +124,14 @@ func TestAdminComponent(t *testing.T) {
func TestEventToMap(t *testing.T) {
var uid int64 = 1234
var epoch = int64(1547127600485)
var etype int8
var etype int8 = 6
var realmID = "realm"
var clientID = "client"
var userID = "user"
var sessionID = "session"
var ipAddr = "ipAddress"
var error = "error"
var username = "test_username"

var event *fb.Event
{
Expand All @@ -143,7 +145,7 @@ func TestEventToMap(t *testing.T) {
var error = builder.CreateString(error)

var key1 = builder.CreateString("username")
var value1 = builder.CreateString("test_username")
var value1 = builder.CreateString(username)
fb.TupleStart(builder)
fb.TupleAddKey(builder, key1)
fb.TupleAddValue(builder, value1)
Expand Down Expand Up @@ -178,20 +180,56 @@ func TestEventToMap(t *testing.T) {
}

var m = eventToMap(event)
assert.Equal(t, strconv.FormatInt(uid, 10), m["uid"])
assert.Equal(t, time.Unix(0, epoch*1000000).Format("2006-01-02T15:04:05.000Z"), m["time"])
assert.Equal(t, fb.EnumNamesEventType[int8(etype)], m["type"])
assert.Equal(t, realmID, m["realmId"])
assert.Equal(t, clientID, m["clientId"])
assert.Equal(t, userID, m["userId"])
assert.Equal(t, sessionID, m["sessionId"])
assert.Equal(t, ipAddr, m["ipAddress"])
assert.Equal(t, error, m["error"])
assert.Equal(t, time.Unix(0, epoch*1000000).UTC().Format("2006-01-02 15:04:05.000"), m["audit_time"])
assert.Equal(t, fb.EnumNamesEventType[int8(etype)], m["kc_event_type"])
assert.Equal(t, realmID, m["realm_name"])
assert.Equal(t, clientID, m["client_id"])
assert.Equal(t, userID, m["user_id"])
assert.Equal(t, username, m["username"])
var f = make(map[string]string)
err := json.Unmarshal([]byte(m["additional_info"]), &f)
assert.Nil(t, err)
assert.Equal(t, strconv.FormatInt(uid, 10), f["uid"])
assert.Equal(t, sessionID, f["session_id"])
assert.Equal(t, ipAddr, f["ip_address"])
assert.Equal(t, error, f["error"])
assert.Equal(t, "", m["ct_event_type"])

}

func TestEventToMapNewCTEvent(t *testing.T) {
var customEvent = "CUSTOM_EVENT"
var etype int8 = 6

var event *fb.Event
{
var builder = flatbuffers.NewBuilder(0)
var key1 = builder.CreateString("ct_event_type")
var value1 = builder.CreateString(customEvent)
fb.TupleStart(builder)
fb.TupleAddKey(builder, key1)
fb.TupleAddValue(builder, value1)
var detail1 = fb.TupleEnd(builder)

fb.EventStartDetailsVector(builder, 1)
builder.PrependUOffsetT(detail1)
var details = builder.EndVector(1)

fb.EventStart(builder)
fb.EventAddDetails(builder, details)
fb.EventAddType(builder, etype)
var eventOffset = fb.EventEnd(builder)
builder.Finish(eventOffset)
event = fb.GetRootAsEvent(builder.FinishedBytes(), 0)
}

var m = eventToMap(event)
assert.Equal(t, customEvent, m["ct_event_type"])

}

func TestEventToMapLogon(t *testing.T) {
var etype int8 = 0
var etype int8

var event *fb.Event
{
Expand Down Expand Up @@ -422,20 +460,24 @@ func TestAdminEventToMap(t *testing.T) {
}

var m = adminEventToMap(adminEvent)
assert.Equal(t, strconv.FormatInt(uid, 10), m["uid"])
assert.Equal(t, time.Unix(0, epoch*1000000).Format("2006-01-02T15:04:05.000Z"), m["time"])
assert.Equal(t, fb.EnumNamesOperationType[int8(optype)], m["operationType"])
assert.Equal(t, realmID, m["realmId"])
assert.Equal(t, resourcePath, m["resourcePath"])
assert.Equal(t, representation, m["representation"])
assert.Equal(t, error, m["error"])

assert.Equal(t, time.Unix(0, epoch*1000000).UTC().Format("2006-01-02 15:04:05.000"), m["audit_time"])
assert.Equal(t, fb.EnumNamesOperationType[int8(optype)], m["kc_operation_type"])
assert.Equal(t, realmID, m["realm_name"])
var f = make(map[string]string)
err := json.Unmarshal([]byte(m["additional_info"]), &f)
assert.Nil(t, err)
assert.Equal(t, strconv.FormatInt(uid, 10), f["uid"])
assert.Equal(t, resourcePath, f["resource_path"])
assert.Equal(t, representation, f["representation"])
assert.Equal(t, error, f["error"])
assert.Equal(t, "ADMIN", m["ct_event_type"])

}

func TestAdminEventToMapAccountCreated(t *testing.T) {
var resourcePath = "users/8caefab3-90d1-492e-87e0-1bf6cecc76ea/role-mappings/realm "
var optype int8 = 0
var optype int8

var adminEvent *fb.AdminEvent
{
Expand Down
Loading