/
accreditationsmodule.go
185 lines (160 loc) · 6.29 KB
/
accreditationsmodule.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
package keycloakb
import (
"context"
"encoding/json"
"strings"
"time"
"github.com/cloudtrust/common-service/configuration"
errorhandler "github.com/cloudtrust/common-service/errors"
"github.com/cloudtrust/common-service/validation"
"github.com/cloudtrust/keycloak-bridge/internal/constants"
kc "github.com/cloudtrust/keycloak-client"
)
var (
dateLayout = constants.SupportedDateLayouts[0]
)
const (
// CredsIDNow identifies the condition for IDNow service
CredsIDNow = configuration.CheckKeyIDNow
// CredsPhysical identifies the condition for physical identification
CredsPhysical = configuration.CheckKeyPhysical
)
// AccreditationsModule interface
type AccreditationsModule interface {
GetUserAndPrepareAccreditations(ctx context.Context, accessToken, realmName, userID, condition string) (kc.UserRepresentation, int, error)
}
// AccredsKeycloakClient is the minimum Keycloak client interface for accreditations
type AccredsKeycloakClient interface {
UpdateUser(accessToken string, realmName, userID string, user kc.UserRepresentation) error
GetUser(accessToken string, realmName, userID string) (kc.UserRepresentation, error)
GetRealm(accessToken string, realmName string) (kc.RealmRepresentation, error)
}
// AdminConfigurationDBModule interface
type AdminConfigurationDBModule interface {
GetAdminConfiguration(context.Context, string) (configuration.RealmAdminConfiguration, error)
}
type accredsModule struct {
keycloakClient AccredsKeycloakClient
confDBModule AdminConfigurationDBModule
logger Logger
}
// AccreditationRepresentation is a representation of accreditations
type AccreditationRepresentation struct {
Type *string `json:"type,omitempty"`
ExpiryDate *string `json:"expiryDate,omitempty"`
Revoked *bool `json:"revoked,omitempty"`
}
// IsUpdated checks if there are changes in provided values.
// These values are provided by pair: first one is the new value (or nil if no update is expected) and the second one is the former value
func IsUpdated(values ...*string) bool {
for i := 0; i < len(values)-1; i += 2 {
var newValue = values[i]
var formerValue = values[i+1]
if newValue != nil && (formerValue == nil || !strings.EqualFold(*newValue, *formerValue)) {
return true
}
}
return false
}
// RevokeAccreditations revokes active accreditations of the given user
func RevokeAccreditations(kcUser *kc.UserRepresentation) {
var kcAccreds = kcUser.GetAttribute(constants.AttrbAccreditations)
if len(kcAccreds) == 0 {
return
}
var newAccreds []string
for _, accred := range kcAccreds {
newAccreds = append(newAccreds, revoke(accred))
}
kcUser.SetAttribute(constants.AttrbAccreditations, newAccreds)
}
func revoke(accredJSON string) string {
var accred AccreditationRepresentation
if err := json.Unmarshal([]byte(accredJSON), &accred); err == nil {
var today = time.Now()
if expiry, err := time.Parse(dateLayout, *accred.ExpiryDate); err == nil && today.Before(expiry) {
var bTrue = true
accred.Revoked = &bTrue
var bytes, _ = json.Marshal(accred)
accredJSON = string(bytes)
}
}
return accredJSON
}
// NewAccreditationsModule creates an accreditations module
func NewAccreditationsModule(keycloakClient AccredsKeycloakClient, confDBModule AdminConfigurationDBModule, logger Logger) AccreditationsModule {
return &accredsModule{
keycloakClient: keycloakClient,
confDBModule: confDBModule,
logger: logger,
}
}
func (am *accredsModule) GetUserAndPrepareAccreditations(ctx context.Context, accessToken, realmName, userID, condition string) (kc.UserRepresentation, int, error) {
var kcUser kc.UserRepresentation
// Gets the realm
var realm, err = am.keycloakClient.GetRealm(accessToken, realmName)
if err != nil {
am.logger.Warn(ctx, "msg", "getKeycloakRealm: can't get realm from KC", "err", err.Error())
return kcUser, 0, errorhandler.CreateInternalServerError("keycloak")
}
// Retrieve admin configuration from configuration DB
var rac configuration.RealmAdminConfiguration
rac, err = am.confDBModule.GetAdminConfiguration(ctx, *realm.ID)
if err != nil {
am.logger.Warn(ctx, "msg", "CreateAccreditations: can't get admin configuration", "err", err.Error())
return kcUser, 0, errorhandler.CreateInternalServerError("keycloak")
}
// Evaluate accreditations to be created
var newAccreds []string
newAccreds, err = am.evaluateAccreditations(ctx, rac.Accreditations, condition)
if err != nil {
am.logger.Warn(ctx, "msg", "Can't evaluate accreditations", "err", err.Error())
return kcUser, 0, err
}
// Get the user from Keycloak
kcUser, err = am.keycloakClient.GetUser(accessToken, realmName, userID)
if err != nil {
am.logger.Warn(ctx, "msg", "CreateAccreditations: can't get Keycloak user", "err", err.Error(), "realm", realmName, "user", userID)
return kcUser, 0, err
}
if len(newAccreds) == 0 {
return kcUser, 0, errorhandler.CreateInternalServerError("noConfiguredAccreditations")
}
// Update attributes in kcUser
var added = 0
var kcAccreds = kcUser.GetAttribute(constants.AttrbAccreditations)
for _, newAccred := range newAccreds {
if !validation.IsStringInSlice(kcAccreds, newAccred) {
kcAccreds = append(kcAccreds, newAccred)
added++
}
}
kcUser.SetAttribute(constants.AttrbAccreditations, kcAccreds)
return kcUser, added, nil
}
func (am *accredsModule) evaluateAccreditations(ctx context.Context, accreds []configuration.RealmAdminAccreditation, condition string) ([]string, error) {
var newAccreds []string
for _, modelAccred := range accreds {
if modelAccred.Condition == nil || *modelAccred.Condition == condition {
var expiry, err = am.convertDurationToDate(ctx, *modelAccred.Validity)
if err != nil {
return nil, err
}
var newAccreditationJSON, _ = json.Marshal(AccreditationRepresentation{
Type: modelAccred.Type,
ExpiryDate: expiry,
})
newAccreds = append(newAccreds, string(newAccreditationJSON))
}
}
return newAccreds, nil
}
func (am *accredsModule) convertDurationToDate(ctx context.Context, validity string) (*string, error) {
var expiryDate, err = validation.AddLargeDurationE(time.Now(), validity)
if err != nil {
am.logger.Warn(ctx, "msg", "convertDurationToDate: can't convert duration", "duration", validity, "err", err.Error())
return nil, errorhandler.CreateInternalServerError("duration-convertion")
}
var res = expiryDate.Format(dateLayout)
return &res, nil
}