@@ -101,27 +101,89 @@ func checkResponse(resp *http.Response, operation string) error {
101101 return fmt .Errorf ("%s failed with status %d: %s" , operation , resp .StatusCode , string (body ))
102102}
103103
104- // ListEligibility retrieves eligible targets for the specified CSP.
104+ // maxPages is the upper bound on pagination requests to guard against infinite loops.
105+ const maxPages = 100
106+
107+ // paginate fetches all pages of a paginated GET endpoint using nextToken cursor.
108+ // buildParams returns the query parameters for each request (nextToken is added automatically).
109+ // decode extracts the items, nextToken pointer, and total from each page response.
110+ // The total is captured from the first page only.
111+ func paginate [T any ](
112+ ctx context.Context ,
113+ s * SCAAccessService ,
114+ route string ,
115+ buildParams func () map [string ]string ,
116+ decode func (io.Reader ) ([]T , * string , int , error ),
117+ errPrefix string ,
118+ ) (allItems []T , total int , _ error ) {
119+ var nextToken * string
120+
121+ for page := range maxPages {
122+ params := buildParams ()
123+ if nextToken != nil {
124+ if params == nil {
125+ params = make (map [string ]string )
126+ }
127+ params ["nextToken" ] = * nextToken
128+ }
129+
130+ var p interface {}
131+ if len (params ) > 0 {
132+ p = params
133+ }
134+
135+ resp , err := s .httpClient .Get (ctx , route , p )
136+ if err != nil {
137+ return nil , 0 , fmt .Errorf ("failed to get %s: %w" , errPrefix , err )
138+ }
139+
140+ if err := checkResponse (resp , errPrefix + " request" ); err != nil {
141+ resp .Body .Close ()
142+ return nil , 0 , err
143+ }
144+
145+ pageItems , nt , pageTotal , decErr := decode (resp .Body )
146+ resp .Body .Close ()
147+ if decErr != nil {
148+ return nil , 0 , fmt .Errorf ("failed to decode %s response: %w" , errPrefix , decErr )
149+ }
150+
151+ allItems = append (allItems , pageItems ... )
152+ if page == 0 {
153+ total = pageTotal
154+ }
155+ nextToken = nt
156+
157+ if nextToken == nil {
158+ return allItems , total , nil
159+ }
160+ }
161+
162+ return nil , 0 , fmt .Errorf ("%s pagination exceeded maximum page limit" , errPrefix )
163+ }
164+
165+ // ListEligibility retrieves all eligible targets for the specified CSP,
166+ // automatically paginating through all pages via nextToken.
105167// GET /api/access/{CSP}/eligibility
106168func (s * SCAAccessService ) ListEligibility (ctx context.Context , csp models.CSP ) (* models.EligibilityResponse , error ) {
107169 route := fmt .Sprintf ("/api/access/%s/eligibility" , csp )
108170
109- resp , err := s .httpClient .Get (ctx , route , nil )
171+ items , total , err := paginate (ctx , s , route ,
172+ func () map [string ]string { return nil },
173+ func (r io.Reader ) ([]models.EligibleTarget , * string , int , error ) {
174+ var page models.EligibilityResponse
175+ if err := json .NewDecoder (r ).Decode (& page ); err != nil {
176+ return nil , nil , 0 , err
177+ }
178+ return page .Response , page .NextToken , page .Total , nil
179+ },
180+ "eligibility" ,
181+ )
110182 if err != nil {
111- return nil , fmt .Errorf ("failed to get eligibility: %w" , err )
112- }
113- defer resp .Body .Close ()
114-
115- if err := checkResponse (resp , "eligibility request" ); err != nil {
116183 return nil , err
117184 }
118185
119- var result models.EligibilityResponse
120- if err := json .NewDecoder (resp .Body ).Decode (& result ); err != nil {
121- return nil , fmt .Errorf ("failed to decode eligibility response: %w" , err )
122- }
123-
124- return & result , nil
186+ return & models.EligibilityResponse {Response : items , Total : total }, nil
125187}
126188
127189// Elevate requests JIT elevation for the specified targets.
@@ -180,55 +242,55 @@ func (s *SCAAccessService) RevokeSessions(ctx context.Context, req *models.Revok
180242 return & result , nil
181243}
182244
183- // ListSessions retrieves active elevated sessions, optionally filtered by CSP.
245+ // ListSessions retrieves all active elevated sessions, optionally filtered by CSP,
246+ // automatically paginating through all pages via nextToken.
184247// GET /api/access/sessions
185248func (s * SCAAccessService ) ListSessions (ctx context.Context , csp * models.CSP ) (* models.SessionsResponse , error ) {
186- route := "/api/access/sessions"
187-
188- var params interface {}
189- if csp != nil {
190- params = map [string ]string {"csp" : string (* csp )}
191- }
192-
193- resp , err := s .httpClient .Get (ctx , route , params )
249+ items , total , err := paginate (ctx , s , "/api/access/sessions" ,
250+ func () map [string ]string {
251+ if csp != nil {
252+ return map [string ]string {"csp" : string (* csp )}
253+ }
254+ return nil
255+ },
256+ func (r io.Reader ) ([]models.SessionInfo , * string , int , error ) {
257+ var page models.SessionsResponse
258+ if err := json .NewDecoder (r ).Decode (& page ); err != nil {
259+ return nil , nil , 0 , err
260+ }
261+ return page .Response , page .NextToken , page .Total , nil
262+ },
263+ "sessions" ,
264+ )
194265 if err != nil {
195- return nil , fmt .Errorf ("failed to get sessions: %w" , err )
196- }
197- defer resp .Body .Close ()
198-
199- if err := checkResponse (resp , "sessions request" ); err != nil {
200266 return nil , err
201267 }
202268
203- var result models.SessionsResponse
204- if err := json .NewDecoder (resp .Body ).Decode (& result ); err != nil {
205- return nil , fmt .Errorf ("failed to decode sessions response: %w" , err )
206- }
207-
208- return & result , nil
269+ return & models.SessionsResponse {Response : items , Total : total }, nil
209270}
210271
211- // ListGroupsEligibility retrieves eligible Entra ID groups for the specified CSP.
272+ // ListGroupsEligibility retrieves all eligible Entra ID groups for the specified CSP,
273+ // automatically paginating through all pages via nextToken.
212274// GET /api/access/{CSP}/eligibility/groups
213275func (s * SCAAccessService ) ListGroupsEligibility (ctx context.Context , csp models.CSP ) (* models.GroupsEligibilityResponse , error ) {
214276 route := fmt .Sprintf ("/api/access/%s/eligibility/groups" , csp )
215277
216- resp , err := s .httpClient .Get (ctx , route , nil )
278+ items , total , err := paginate (ctx , s , route ,
279+ func () map [string ]string { return nil },
280+ func (r io.Reader ) ([]models.GroupsEligibleTarget , * string , int , error ) {
281+ var page models.GroupsEligibilityResponse
282+ if err := json .NewDecoder (r ).Decode (& page ); err != nil {
283+ return nil , nil , 0 , err
284+ }
285+ return page .Response , page .NextToken , page .Total , nil
286+ },
287+ "groups eligibility" ,
288+ )
217289 if err != nil {
218- return nil , fmt .Errorf ("failed to get groups eligibility: %w" , err )
219- }
220- defer resp .Body .Close ()
221-
222- if err := checkResponse (resp , "groups eligibility request" ); err != nil {
223290 return nil , err
224291 }
225292
226- var result models.GroupsEligibilityResponse
227- if err := json .NewDecoder (resp .Body ).Decode (& result ); err != nil {
228- return nil , fmt .Errorf ("failed to decode groups eligibility response: %w" , err )
229- }
230-
231- return & result , nil
293+ return & models.GroupsEligibilityResponse {Response : items , Total : total }, nil
232294}
233295
234296// ElevateGroups requests JIT elevation for the specified Entra ID groups.
0 commit comments