@@ -5,8 +5,11 @@ import (
55 "fmt"
66 "net/http"
77 "net/url"
8+ "strings"
89
910 "github.com/conductorone/baton-sdk/pkg/uhttp"
11+ "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
12+ "go.uber.org/zap"
1013 "google.golang.org/grpc/codes"
1114 "google.golang.org/grpc/status"
1215)
@@ -41,8 +44,9 @@ const (
4144)
4245
4346type Client struct {
44- wrapper * uhttp.BaseHttpClient
45- scope Scope
47+ wrapper * uhttp.BaseHttpClient
48+ scope Scope
49+ workspaceIDs map [string ]bool
4650}
4751
4852func NewClient (httpClient * http.Client ) * Client {
@@ -108,29 +112,112 @@ func (c *Client) WorkspaceId() (string, error) {
108112 }
109113}
110114
111- // If client have access to multiple workspaces, method `WorkspaceIds`
112- // returns list of workspace ids otherwise it returns error.
113- func (c * Client ) WorkspaceIDs (ctx context.Context ) ([]string , error ) {
114- workspaceIDs := make ([]string , 0 )
115+ func isPermissionDeniedErr (err error ) bool {
116+ e , ok := status .FromError (err )
117+ if ok && e .Code () == codes .PermissionDenied {
118+ return true
119+ }
120+ // In most cases the error code is unknown and the error message contains "status 403".
121+ if (! ok || e .Code () == codes .Unknown ) && strings .Contains (err .Error (), "status 403" ) {
122+ return true
123+ }
124+ return false
125+ }
115126
116- if c .IsUserScoped () {
117- workspaces , err := c .GetAllWorkspaces (ctx )
118- if err != nil {
119- return nil , err
127+ func (c * Client ) checkPermissions (ctx context.Context , workspace * Workspace ) (bool , error ) {
128+ l := ctxzap .Extract (ctx )
129+ logMissingPermission := func (obj string , err error ) {
130+ l .Error (
131+ "missing permission to list object in workspace" ,
132+ zap .String ("workspace" , workspace .Slug ),
133+ zap .String ("workspace id" , workspace .Id ),
134+ zap .String ("object" , obj ),
135+ zap .Error (err ),
136+ )
137+ }
138+ paginationVars := PaginationVars {
139+ Limit : 1 ,
140+ Page : "" ,
141+ }
142+ _ , err := c .GetWorkspaceUserGroups (ctx , workspace .Id )
143+ if err != nil {
144+ if isPermissionDeniedErr (err ) {
145+ logMissingPermission ("userGroups" , err )
146+ return false , nil
120147 }
121-
122- for _ , workspace := range workspaces {
123- workspaceIDs = append (workspaceIDs , workspace .Id )
148+ return false , err
149+ }
150+ _ , _ , err = c .GetWorkspaceMembers (ctx , workspace .Id , paginationVars )
151+ if err != nil {
152+ if isPermissionDeniedErr (err ) {
153+ logMissingPermission ("users" , err )
154+ return false , nil
124155 }
156+ return false , err
157+ }
158+ _ , _ , err = c .GetWorkspaceProjects (ctx , workspace .Id , paginationVars )
159+ if err != nil {
160+ if isPermissionDeniedErr (err ) {
161+ logMissingPermission ("projects" , err )
162+ return false , nil
163+ }
164+ return false , err
165+ }
166+ return true , nil
167+ }
168+
169+ func (c * Client ) filterWorkspaces (ctx context.Context , workspaces []Workspace ) ([]Workspace , error ) {
170+ filteredWorkspaces := make ([]Workspace , 0 )
125171
126- if len (workspaceIDs ) == 0 {
127- return nil , status .Error (codes .NotFound , "no workspaces found" )
172+ for _ , workspace := range workspaces {
173+ // We call this function in order to initialize the workspaceID's map. In that case we need to return all workspaces,
174+ // so they can be filtered and only the valid ones are set in the workspaceIds map.
175+ _ , ok := c .workspaceIDs [workspace .Id ]
176+ if c .workspaceIDs != nil && ! ok {
177+ continue
128178 }
129- } else {
130- return nil , status .Error (codes .InvalidArgument , "client is not user scoped" )
179+
180+ filteredWorkspaces = append (filteredWorkspaces , workspace )
181+ }
182+
183+ return filteredWorkspaces , nil
184+ }
185+
186+ // If client have access to multiple workspaces, method `WorkspaceIDs`
187+ // returns list of workspace ids otherwise it returns error.
188+ func (c * Client ) SetWorkspaceIDs (ctx context.Context , workspaceIDs []string ) error {
189+ if ! c .IsUserScoped () {
190+ return status .Error (codes .InvalidArgument , "client is not user scoped" )
191+ }
192+ c .workspaceIDs = make (map [string ]bool )
193+ givenWorkspaceIDs := make (map [string ]bool )
194+ for _ , workspaceId := range workspaceIDs {
195+ givenWorkspaceIDs [workspaceId ] = true
131196 }
132197
133- return workspaceIDs , nil
198+ workspaces , err := c .GetAllWorkspaces (ctx )
199+ if err != nil {
200+ return err
201+ }
202+
203+ for _ , workspace := range workspaces {
204+ workspace := workspace
205+ if _ , ok := givenWorkspaceIDs [workspace .Id ]; ! ok && len (givenWorkspaceIDs ) > 0 {
206+ continue
207+ }
208+ ok , err := c .checkPermissions (ctx , & workspace )
209+ if err != nil {
210+ return err
211+ }
212+ if ! ok {
213+ continue
214+ }
215+ c .workspaceIDs [workspace .Id ] = true
216+ }
217+ if len (c .workspaceIDs ) == 0 {
218+ return status .Error (codes .Unauthenticated , "no authenticated workspaces found" )
219+ }
220+ return nil
134221}
135222
136223// GetWorkspaces lists all workspaces current user belongs to.
@@ -150,7 +237,10 @@ func (c *Client) GetWorkspaces(ctx context.Context, getWorkspacesVars Pagination
150237 prepareFilters ("" ),
151238 },
152239 )
153-
240+ if err != nil {
241+ return nil , "" , err
242+ }
243+ workspacesResponse .Values , err = c .filterWorkspaces (ctx , workspacesResponse .Values )
154244 if err != nil {
155245 return nil , "" , err
156246 }
@@ -202,8 +292,10 @@ func (c *Client) GetWorkspace(ctx context.Context, workspaceId string) (*Workspa
202292 prepareFilters ("" ),
203293 },
204294 )
205-
206295 if err != nil {
296+ if isPermissionDeniedErr (err ) {
297+ return nil , status .Error (codes .PermissionDenied , "missing permission to get workspace" )
298+ }
207299 return nil , err
208300 }
209301
@@ -228,7 +320,6 @@ func (c *Client) GetWorkspaceMembers(ctx context.Context, workspaceId string, ge
228320 prepareFilters ("" , "-*.workspace" ),
229321 },
230322 )
231-
232323 if err != nil {
233324 return nil , "" , err
234325 }
0 commit comments