Skip to content

Commit d30fa05

Browse files
authored
feat: Add SAML federation group mapping, preview and check support (#15)
1 parent 86295f3 commit d30fa05

File tree

8 files changed

+818
-15
lines changed

8 files changed

+818
-15
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Package groupmappings provides a set of functions for interacting with
2+
// the Selectel SAML Federation Group Mappings API.
3+
package groupmappings
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
package groupmappings
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
7+
"errors"
8+
"net/http"
9+
"net/url"
10+
11+
"github.com/selectel/iam-go/iamerrors"
12+
"github.com/selectel/iam-go/internal/client"
13+
)
14+
15+
const apiVersion = "v1"
16+
17+
// Service is used to communicate with the Federations Group Mappings API.
18+
type Service struct {
19+
baseClient *client.BaseClient
20+
}
21+
22+
// New Initialises Service with the given client.
23+
func New(baseClient *client.BaseClient) *Service {
24+
return &Service{
25+
baseClient: baseClient,
26+
}
27+
}
28+
29+
// List returns a list of mappings for the Federation.
30+
func (s *Service) List(ctx context.Context, federationID string) (*GroupMappingsResponse, error) {
31+
if federationID == "" {
32+
return nil, iamerrors.Error{Err: iamerrors.ErrFederationIDRequired, Desc: "No federationID was provided."}
33+
}
34+
35+
path, err := url.JoinPath(apiVersion, "federations", "saml", federationID, "group-mappings")
36+
if err != nil {
37+
return nil, iamerrors.Error{Err: iamerrors.ErrInternalAppError, Desc: err.Error()}
38+
}
39+
40+
response, err := s.baseClient.DoRequest(ctx, client.DoRequestInput{
41+
Body: nil,
42+
Method: http.MethodGet,
43+
Path: path,
44+
})
45+
if err != nil {
46+
//nolint:wrapcheck // DoRequest already wraps the error.
47+
return nil, err
48+
}
49+
50+
var mappings GroupMappingsResponse
51+
err = client.UnmarshalJSON(response, &mappings)
52+
if err != nil {
53+
return nil, iamerrors.Error{Err: iamerrors.ErrInternalAppError, Desc: err.Error()}
54+
}
55+
56+
return &mappings, nil
57+
}
58+
59+
// Update updates mappings for the Federation.
60+
func (s *Service) Update(
61+
ctx context.Context, federationID string, input GroupMappingsRequest,
62+
) error {
63+
if federationID == "" {
64+
return iamerrors.Error{Err: iamerrors.ErrFederationIDRequired, Desc: "No federationID was provided."}
65+
}
66+
67+
path, err := url.JoinPath(apiVersion, "federations", "saml", federationID, "group-mappings")
68+
if err != nil {
69+
return iamerrors.Error{Err: iamerrors.ErrInternalAppError, Desc: err.Error()}
70+
}
71+
72+
body, err := json.Marshal(input)
73+
if err != nil {
74+
return iamerrors.Error{Err: iamerrors.ErrInternalAppError, Desc: err.Error()}
75+
}
76+
77+
_, err = s.baseClient.DoRequest(ctx, client.DoRequestInput{
78+
Body: bytes.NewReader(body),
79+
Method: http.MethodPut,
80+
Path: path,
81+
})
82+
if err != nil {
83+
//nolint:wrapcheck // DoRequest already wraps the error.
84+
return err
85+
}
86+
87+
return nil
88+
}
89+
90+
// Add creates mapping between internal and external group.
91+
func (s *Service) Add(
92+
ctx context.Context, federationID, groupID, externalGroupID string,
93+
) error {
94+
path, err := buildExternalGroupMappingPath(federationID, groupID, externalGroupID)
95+
if err != nil {
96+
return err
97+
}
98+
99+
_, err = s.baseClient.DoRequest(ctx, client.DoRequestInput{
100+
Body: nil,
101+
Method: http.MethodPut,
102+
Path: path,
103+
})
104+
if err != nil {
105+
//nolint:wrapcheck // DoRequest already wraps the error.
106+
return err
107+
}
108+
109+
return nil
110+
}
111+
112+
// Delete deletes mapping between internal and external group.
113+
func (s *Service) Delete(
114+
ctx context.Context, federationID, groupID, externalGroupID string,
115+
) error {
116+
path, err := buildExternalGroupMappingPath(federationID, groupID, externalGroupID)
117+
if err != nil {
118+
return err
119+
}
120+
121+
_, err = s.baseClient.DoRequest(ctx, client.DoRequestInput{
122+
Body: nil,
123+
Method: http.MethodDelete,
124+
Path: path,
125+
})
126+
if err != nil {
127+
//nolint:wrapcheck // DoRequest already wraps the error.
128+
return err
129+
}
130+
131+
return nil
132+
}
133+
134+
// Exists checks that internal and external groups are mapped.
135+
func (s *Service) Exists(
136+
ctx context.Context, federationID, groupID, externalGroupID string,
137+
) (bool, error) {
138+
path, err := buildExternalGroupMappingPath(federationID, groupID, externalGroupID)
139+
if err != nil {
140+
return false, err
141+
}
142+
143+
_, err = s.baseClient.DoRequest(ctx, client.DoRequestInput{
144+
Body: nil,
145+
Method: http.MethodHead,
146+
Path: path,
147+
})
148+
if err != nil {
149+
if errors.Is(err, iamerrors.ErrFederationNotFound) ||
150+
errors.Is(err, iamerrors.ErrGroupNotFound) ||
151+
errors.Is(err, iamerrors.ErrUserOrGroupNotFound) {
152+
return false, nil
153+
}
154+
155+
//nolint:wrapcheck // DoRequest already wraps the error.
156+
return false, err
157+
}
158+
159+
return true, nil
160+
}
161+
162+
func buildExternalGroupMappingPath(
163+
federationID, groupID, externalGroupID string,
164+
) (string, error) {
165+
if federationID == "" {
166+
return "", iamerrors.Error{Err: iamerrors.ErrFederationIDRequired, Desc: "No federationID was provided."}
167+
}
168+
if groupID == "" {
169+
return "", iamerrors.Error{Err: iamerrors.ErrGroupIDRequired, Desc: "No groupID was provided."}
170+
}
171+
if externalGroupID == "" {
172+
return "", iamerrors.Error{Err: iamerrors.ErrInputDataRequired, Desc: "No externalGroupID was provided."}
173+
}
174+
175+
path, err := url.JoinPath(
176+
apiVersion,
177+
"federations",
178+
"saml",
179+
federationID,
180+
"group-mappings",
181+
groupID,
182+
"external-groups",
183+
externalGroupID,
184+
)
185+
if err != nil {
186+
return "", iamerrors.Error{Err: iamerrors.ErrInternalAppError, Desc: err.Error()}
187+
}
188+
189+
return path, nil
190+
}

0 commit comments

Comments
 (0)