1 package edgebsl
2
3 import (
4 "context"
5 "errors"
6 "fmt"
7 "math/rand"
8 "net/http"
9 "reflect"
10 "strings"
11 "unicode"
12
13 "github.com/go-resty/resty/v2"
14 "github.com/sethvargo/go-password/password"
15
16 bslerror "edge-infra.dev/pkg/edge/api/apierror/bsl"
17 "edge-infra.dev/pkg/edge/api/bsl/types"
18 "edge-infra.dev/pkg/edge/api/graph/model"
19 "edge-infra.dev/pkg/edge/bsl"
20 logger "edge-infra.dev/pkg/lib/logging"
21 )
22
23 var (
24 RoleMap = map[model.Role]interface{}{
25 model.RoleEdgeOrgAdmin: model.AllEdgeOrgAdmin,
26 model.RoleEdgeBannerAdmin: model.AllEdgeBannerAdmin,
27 model.RoleEdgeBannerViewer: model.AllEdgeBannerViewer,
28 model.RoleEdgeSuperAdmin: model.AllEdgeSuperAdmin,
29 model.RoleEdgeBannerOperator: model.AllEdgeBannerOperator,
30 model.RoleEdgeEngineeringLeads: model.AllEdgeEngineeringLeads,
31 model.RoleEdgeOiAdmin: model.AllEdgeOiAdmin,
32 model.RoleEdgeSuperUser: []string{},
33 model.RoleEdgeL1: []string{},
34 model.RoleEdgeL2: []string{},
35 model.RoleEdgeL3: []string{},
36 model.RoleEdgeL4: []string{},
37 }
38
39 ErrorEmptyPage = errors.New("no page data was returned")
40
41 ErrorPaginationDone = errors.New("last page of orgs")
42
43 ErrorInvalidCredentials = errors.New("user credentials are invalid")
44
45 ErrorInsufficientPrivileges = errors.New("request action requires elevated privileges")
46
47 ErrorFailedValidation = errors.New("the request failed validation")
48
49 ErrorResourceAlreadyExists = errors.New("the resources already exists")
50
51 ErrorGeneric = errors.New("an error occurred")
52 )
53
54
55 func (b *BslConfig) GetBSLClient(ctx context.Context, org string) (*bsl.Request, error) {
56 if b.Client == nil {
57 bslClient := bsl.NewBSLClient(types.BSPConfig{
58 Endpoint: b.RootURI,
59 Root: b.RootOrg,
60 OrganizationPrefix: b.OrgPrefix,
61 })
62 bslClient.SetDefaultAccessKey(b.AccessKey.SharedKey, b.AccessKey.SecretKey)
63 bslClient.SetTimeout(defaultTimeout)
64 bslClient.OnAfterResponse(func(_ *resty.Client, response *resty.Response) error {
65 return handleHTTPStatusError(response.StatusCode())
66 })
67 b.Client = bslClient
68 }
69 req, err := b.Client.WithBackendOrgAccessKey(ctx, org)
70 if err != nil {
71 return nil, err
72 }
73 return req.SetExactOrg(org), err
74 }
75
76 func (b *BslConfig) GetAllEdgeOrgs(ctx context.Context) ([]AllEdgeOrgsPageContent, error) {
77
78 var fullOrgsList []AllEdgeOrgsPageContent
79
80 allEdgeOrgs, err := b.GetBSLOrgs(ctx)
81
82 if err != nil {
83 return nil, err
84 }
85
86 fullOrgsList = append(fullOrgsList, allEdgeOrgs.PageContent...)
87
88 var envFilteredList []AllEdgeOrgsPageContent
89 for _, org := range fullOrgsList {
90 if strings.HasPrefix(org.OrganizationName, b.OrgPrefix) && (org.Parent || strings.Count(org.FullyQualifiedName, "/") < 4) {
91 envFilteredList = append(envFilteredList, org)
92 }
93 }
94
95 return envFilteredList, nil
96 }
97
98 func (b *BslConfig) GetBSLOrgs(ctx context.Context) (*AllEdgeOrgs, error) {
99 client, err := b.GetBSLClient(ctx, "edge")
100 if err != nil {
101 return nil, err
102 }
103
104 allEdgeOrgs := &AllEdgeOrgs{}
105
106 if err := client.JSON(http.MethodGet, getEdgeOrgs, allEdgeOrgs); err != nil {
107 return nil, err
108 }
109
110 return allEdgeOrgs, nil
111 }
112
113
114 func (b *BslConfig) CreateEdgeOrgGroups(ctx context.Context, organizationName string) error {
115 client, err := b.GetBSLClient(ctx, organizationName)
116 if err != nil {
117 return err
118 }
119 for key := range RoleMap {
120 g := EdgeOrgGroup{
121 GroupName: string(key),
122 GroupDescription: fmt.Sprintf("%s edge user group", key),
123 }
124
125 if err := client.SetPayload(g).Post(createBslGroupPath); err != nil {
126 var e *bslerror.Error
127 if errors.As(err, &e) && !e.Is(ErrorResourceAlreadyExists) {
128 return err
129 }
130 }
131 }
132 return nil
133 }
134
135
136
137
138
139 func (b *BslConfig) CleanUpGroupRoles(ctx context.Context, organizationName string) error {
140 for userGroupName, fullRolesList := range RoleMap {
141
142 mappedRoles := reflect.ValueOf(fullRolesList)
143 mappedRolesLen := mappedRoles.Len()
144 if mappedRolesLen > 0 {
145
146 groupRolesList, err := b.GetGroupRoles(ctx, userGroupName.String(), organizationName)
147 if err != nil {
148 return err
149 }
150
151
152 rolesToRevoke := getRolesToRevoke(mappedRoles, groupRolesList)
153
154 if len(rolesToRevoke) > 0 {
155 err := b.RevokeRolesFromGroup(ctx, organizationName, userGroupName, rolesToRevoke)
156
157 if err != nil {
158 return err
159 }
160 }
161 }
162 }
163 return nil
164 }
165
166
167
168
169
170
171 func getRolesToRevoke(mappedRolesForUserGroup reflect.Value, userGroupRolesList []BSLRole) []string {
172 rolesToRevoke := make([]string, 0)
173
174 for _, grantedGroupRole := range userGroupRolesList {
175
176
177 if !hasRole(grantedGroupRole.Name, mappedRolesForUserGroup) {
178 rolesToRevoke = append(rolesToRevoke, grantedGroupRole.Name)
179 }
180 }
181
182 return rolesToRevoke
183 }
184
185
186
187
188
189 func hasRole(grantedRole string, mappedRolesForUserGroup reflect.Value) bool {
190 roleFound := false
191
192 for i := 0; i < mappedRolesForUserGroup.Len(); i++ {
193 roleNameInMap := mappedRolesForUserGroup.Index(i).String()
194
195 if roleNameInMap == grantedRole {
196 return true
197 }
198 }
199
200 return roleFound
201 }
202
203 func (b *BslConfig) GetGroupRoles(ctx context.Context, userGroupName, organizationName string) ([]BSLRole, error) {
204 client, err := b.GetBSLClient(ctx, organizationName)
205 if err != nil {
206 return nil, err
207 }
208
209 groupList := &GetGroupRolesResponse{}
210 getUserGroupRolesURL := fmt.Sprintf(getUserGroupGrantedRoles, userGroupName)
211 if err := client.JSON(http.MethodGet, getUserGroupRolesURL, groupList); err != nil {
212 return nil, err
213 }
214
215 return groupList.Content, nil
216 }
217
218
219 func (b *BslConfig) RevokeRolesFromGroup(ctx context.Context, organizationName string, userGroupName model.Role, roles []string) error {
220 log := logger.NewLogger()
221 rolesToRevoke := createBSLRole(userGroupName, roles)
222
223 client, err := b.GetBSLClient(ctx, organizationName)
224 if err != nil {
225 return err
226 }
227
228
229 if err := client.SetPayload(rolesToRevoke).Post(revokeBslRoleFromGroupPath); err != nil {
230 log.Error(err, "Error removing roles from BSL user group", rolesToRevoke)
231 return err
232 }
233
234 log.Info("Removed Roles from User Group", rolesToRevoke)
235
236 return nil
237 }
238
239
240 func (b *BslConfig) AssignRolesToGroups(ctx context.Context, organizationName string) error {
241 client, err := b.GetBSLClient(ctx, organizationName)
242 if err != nil {
243 return err
244 }
245 for k, v := range RoleMap {
246 assignRolesToGroupPayload := createBSLRole(k, v)
247 hasRoleMapping := len(assignRolesToGroupPayload.Roles) > 0
248
249 if hasRoleMapping {
250 if err := client.SetPayload(assignRolesToGroupPayload).Post(grantBslRolePath); err != nil {
251 return err
252 }
253 }
254 }
255 return nil
256 }
257
258 func createBSLRole(k model.Role, v interface{}) *BSLRoles {
259 r := &BSLRoles{
260 Groups: []EdgeOrgGroup{
261 {
262 GroupName: string(k),
263 },
264 },
265 Roles: []BSLRole{},
266 }
267 rv := reflect.ValueOf(v)
268 for i := 0; i < rv.Len(); i++ {
269 r.Roles = append(r.Roles, BSLRole{Name: rv.Index(i).String()})
270 }
271 return r
272 }
273
274
275 func (b *BslConfig) CreateBSLUser(ctx context.Context, organizationName, BFFUsername string) (string, error) {
276 client, err := b.GetBSLClient(ctx, organizationName)
277 if err != nil {
278 return "", err
279 }
280 if err := client.SetPayload(newBFFUserRequest(BFFUsername)).Post(createBslUserPath); err != nil {
281 return "", err
282 }
283 password, err := generateBFFUserPassword()
284 if err != nil {
285 return "", err
286 }
287 return password, b.CreateBSLUserPassword(ctx, organizationName, BFFUsername, password)
288 }
289
290 func (b *BslConfig) CreateBSLUserAccessKey(ctx context.Context, organization, username string) (*AccessKeyResponse, error) {
291 client, err := b.GetBSLClient(ctx, organization)
292 if err != nil {
293 return nil, err
294 }
295 res := &AccessKeyResponse{}
296 return res, client.SetPayload(newAccessKeyRequest(organization, username)).JSON(http.MethodPost, createUserAccessKeyPath, res)
297 }
298
299
300 func (b *BslConfig) AssignBSLUserToGroup(ctx context.Context, organizationName, groupName, username string) error {
301 client, err := b.GetBSLClient(ctx, organizationName)
302 if err != nil {
303 return err
304 }
305 return client.SetPayload(newEdgeOrgGroupMembership(groupName, username)).Post(grantRoleToRootUser)
306 }
307
308
309 func (b *BslConfig) CreateBSLUserPassword(ctx context.Context, organization, username, password string) error {
310 client, err := b.GetBSLClient(ctx, organization)
311 if err != nil {
312 return err
313 }
314 return client.SetPayload(newBFFUserPasswordResetRequest(username, password)).Put(resetBslUserPasswordPath)
315 }
316
317
318 func (b *BslConfig) CreateEnterpriseUnitType(ctx context.Context, organization, name, description string) error {
319 client, err := b.GetBSLClient(ctx, organization)
320 if err != nil {
321 return err
322 }
323 return client.SetPayload(newEnterpriseTypeRequest(name, description)).Post(createEnterpriseUnitType)
324 }
325
326
327 func newBFFUserRequest(username string) *BFFUserRequest {
328 return &BFFUserRequest{
329 Username: username,
330 Email: "bffuser@ncr.com",
331 FullName: "Edge BFF",
332 GivenName: "BFF",
333 FamilyName: "Edge",
334 TelephoneNumber: "000-000-0000",
335 Status: "ACTIVE",
336 Address: &BFFUserAddress{
337 City: "Atlanta",
338 Country: "USA",
339 PostalCode: "30303",
340 State: "GA",
341 Street: "Spring St",
342 },
343 }
344 }
345
346
347 func newEnterpriseTypeRequest(name, description string) *EnterpriseType {
348 return &EnterpriseType{
349 Name: name,
350 Description: description,
351 }
352 }
353
354
355 func newEdgeOrgGroupMembership(groupName string, username string) *EdgeOrgGroupMembership {
356 return &EdgeOrgGroupMembership{
357 GroupName: groupName,
358 Members: []EdgeOrgGroupMember{
359 {
360 Username: username,
361 },
362 },
363 }
364 }
365
366
367 func newBFFUserPasswordResetRequest(username, password string) *BFFUserPasswordReset {
368 return &BFFUserPasswordReset{
369 Username: username,
370 Password: password,
371 }
372 }
373
374 func newAccessKeyRequest(organization, username string) *AccessKeyRequest {
375 return &AccessKeyRequest{UserID: UserID{Username: bsl.CreateFullAccountName(&types.AuthUser{Organization: organization, Username: username})}}
376 }
377
378
379
380 func OrgNameToK8sName(name string) string {
381 name = strings.ToLower(name)
382 res := strings.Trim(name, "//")
383 rootOrgSepIndex := strings.Index(res, "/")
384 res = strings.ReplaceAll(res, " ", "-")
385 res = strings.ReplaceAll(res, "/", "-")
386 return res[rootOrgSepIndex+1:]
387 }
388
389
390 func generateBFFUserPassword() (string, error) {
391 pass, err := password.Generate(20, 10, 0, false, true)
392 if err != nil {
393 return "", err
394 }
395 allowedSpecialChars := []string{"!", "@", "#", "$", "*", "&", "(", ")"}
396 randomSpecialChar := allowedSpecialChars[rand.Intn((len(allowedSpecialChars)))]
397 pass = fmt.Sprintf("%s%s", pass, randomSpecialChar)
398 if !checkPasswordRequirements(pass) {
399 pass, _ = generateBFFUserPassword()
400 }
401 return pass, err
402 }
403
404
405
406
407
408 func checkPasswordRequirements(password string) bool {
409 var uppercase, lowercase, digit = false, false, false
410 for _, value := range password {
411 if unicode.IsUpper(value) {
412 uppercase = true
413 } else if unicode.IsLower(value) {
414 lowercase = true
415 } else if unicode.IsDigit(value) {
416 digit = true
417 }
418 }
419 return uppercase && lowercase && digit
420 }
421
422
423 func newBSLUsername(length int, suffix bool) string {
424 letterBytes := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
425 b := make([]byte, length)
426 for i := range b {
427 b[i] = letterBytes[rand.Intn(len(letterBytes))]
428 }
429 if suffix {
430 return fmt.Sprintf("%s-%s", strings.ToLower(string(b)), BFFUsername)
431 }
432 return strings.ToLower(string(b))
433 }
434
435
436 func handleHTTPStatusError(status int) error {
437 switch status {
438 case http.StatusOK:
439 return nil
440 case http.StatusUnauthorized:
441 return ErrorInvalidCredentials
442 case http.StatusForbidden:
443 return ErrorInsufficientPrivileges
444 case http.StatusBadRequest:
445 return ErrorFailedValidation
446 case http.StatusConflict:
447 return ErrorResourceAlreadyExists
448 case http.StatusNoContent:
449 return nil
450 default:
451 return ErrorGeneric
452 }
453 }
454
View as plain text