22using Microsoft . AspNetCore . Cors ;
33using Microsoft . AspNetCore . Mvc ;
44using Microsoft . Extensions . Options ;
5+ using rubberduckvba . Server . Model . Entity ;
56using rubberduckvba . Server . Services ;
7+ using System . Security . Principal ;
68
79namespace rubberduckvba . Server . Api . Admin ;
810
911[ ApiController ]
10- public class AdminController ( ConfigurationOptions options , HangfireLauncherService hangfire , CacheService cache ) : ControllerBase
12+ [ EnableCors ( CorsPolicies . AllowAll ) ]
13+ public class AdminController ( ConfigurationOptions options , HangfireLauncherService hangfire , CacheService cache , IAuditService audits ) : ControllerBase
1114{
1215 /// <summary>
1316 /// Enqueues a job that updates xmldoc content from the latest release/pre-release tags.
1417 /// </summary>
1518 /// <returns>The unique identifier of the enqueued job.</returns>
16- [ Authorize ( "github" ) ]
17- [ EnableCors ( CorsPolicies . AllowAuthenticated ) ]
19+ [ Authorize ( "github" , Roles = RDConstants . Roles . AdminRole ) ]
1820 [ HttpPost ( "admin/update/xmldoc" ) ]
1921 public IActionResult UpdateXmldocContent ( )
2022 {
@@ -26,27 +28,169 @@ public IActionResult UpdateXmldocContent()
2628 /// Enqueues a job that gets the latest release/pre-release tags and their respective assets, and updates the installer download stats.
2729 /// </summary>
2830 /// <returns>The unique identifier of the enqueued job.</returns>
29- [ Authorize ( "github" ) ]
30- [ EnableCors ( CorsPolicies . AllowAuthenticated ) ]
31+ [ Authorize ( "github" , Roles = RDConstants . Roles . AdminRole ) ]
3132 [ HttpPost ( "admin/update/tags" ) ]
3233 public IActionResult UpdateTagMetadata ( )
3334 {
3435 var jobId = hangfire . UpdateTagMetadata ( ) ;
3536 return Ok ( jobId ) ;
3637 }
3738
38- [ Authorize ( "github" ) ]
39- [ EnableCors ( CorsPolicies . AllowAuthenticated ) ]
39+ [ Authorize ( "github" , Roles = RDConstants . Roles . AdminRole ) ]
4040 [ HttpPost ( "admin/cache/clear" ) ]
4141 public IActionResult ClearCache ( )
4242 {
4343 cache . Clear ( ) ;
4444 return Ok ( ) ;
4545 }
4646
47+ [ Authorize ( "github" , Roles = $ "{ RDConstants . Roles . AdminRole } ,{ RDConstants . Roles . ReviewerRole } ,{ RDConstants . Roles . WriterRole } ") ]
48+ [ HttpGet ( "admin/audits/pending" ) ]
49+ public async Task < IActionResult > GetPendingAudits ( )
50+ {
51+ var edits = await audits . GetPendingItems < FeatureEditViewEntity > ( User . Identity ) ;
52+ var ops = await audits . GetPendingItems < FeatureOpEntity > ( User . Identity ) ;
53+
54+ return Ok ( new { edits = edits . ToArray ( ) , other = ops . ToArray ( ) } ) ;
55+ }
56+
57+ [ Authorize ( "github" , Roles = $ "{ RDConstants . Roles . AdminRole } ,{ RDConstants . Roles . ReviewerRole } ,{ RDConstants . Roles . WriterRole } ") ]
58+ [ HttpGet ( "profile/activity" ) ]
59+ public async Task < IActionResult > GetUserActivity ( )
60+ {
61+ if ( User . Identity is not IIdentity identity )
62+ {
63+ // this is arguably a bug in the authentication middleware, but we can handle it gracefully here
64+ return Unauthorized ( "User identity is not available." ) ;
65+ }
66+
67+ var activity = await audits . GetAllActivity ( identity ) ;
68+ return Ok ( activity ) ;
69+ }
70+
71+ private static readonly AuditActivityType [ ] EditActivityTypes = [
72+ AuditActivityType . SubmitEdit ,
73+ AuditActivityType . ApproveEdit ,
74+ AuditActivityType . RejectEdit
75+ ] ;
76+
77+ private static readonly AuditActivityType [ ] OpActivityTypes = [
78+ AuditActivityType . SubmitCreate ,
79+ AuditActivityType . ApproveCreate ,
80+ AuditActivityType . RejectCreate ,
81+ AuditActivityType . SubmitDelete ,
82+ AuditActivityType . ApproveDelete ,
83+ AuditActivityType . RejectDelete
84+ ] ;
85+
86+ [ Authorize ( "github" , Roles = $ "{ RDConstants . Roles . AdminRole } ,{ RDConstants . Roles . ReviewerRole } ") ]
87+ [ HttpGet ( "admin/audits/{id}" ) ]
88+ public async Task < IActionResult > GetAudit ( [ FromRoute ] int id , [ FromQuery ] string type )
89+ {
90+ if ( ! Enum . TryParse < AuditActivityType > ( type , ignoreCase : true , out var validType ) )
91+ {
92+ return BadRequest ( "Invalid activity type." ) ;
93+ }
94+
95+ var edit = ( FeatureEditViewEntity ? ) null ;
96+ var op = ( FeatureOpEntity ? ) null ;
97+
98+ if ( EditActivityTypes . Contains ( validType ) )
99+ {
100+ edit = await audits . GetItem < FeatureEditViewEntity > ( id ) ;
101+ }
102+ else if ( OpActivityTypes . Contains ( validType ) )
103+ {
104+ op = await audits . GetItem < FeatureOpEntity > ( id ) ;
105+ }
106+
107+ return Ok ( new { edits = new [ ] { edit } , other = op is null ? [ ] : new [ ] { op } } ) ;
108+ }
109+
110+ [ Authorize ( "github" , Roles = $ "{ RDConstants . Roles . AdminRole } ,{ RDConstants . Roles . ReviewerRole } ") ]
111+ [ HttpGet ( "admin/audits/feature/{featureId}" ) ]
112+ public async Task < IActionResult > GetPendingAudits ( [ FromRoute ] int featureId )
113+ {
114+ var edits = await audits . GetPendingItems < FeatureEditEntity > ( User . Identity , featureId ) ;
115+ var ops = await audits . GetPendingItems < FeatureOpEntity > ( User . Identity , featureId ) ;
116+
117+ return Ok ( new { edits = edits . ToArray ( ) , other = ops . ToArray ( ) } ) ;
118+ }
119+
120+ [ Authorize ( "github" , Roles = $ "{ RDConstants . Roles . AdminRole } ,{ RDConstants . Roles . ReviewerRole } ") ]
121+ [ HttpPost ( "admin/audits/approve/{id}" ) ]
122+ public async Task < IActionResult > ApprovePendingAudit ( [ FromRoute ] int id )
123+ {
124+ if ( User . Identity is not IIdentity identity )
125+ {
126+ // this is arguably a bug in the authentication middleware, but we can handle it gracefully here
127+ return Unauthorized ( "User identity is not available." ) ;
128+ }
129+
130+ var edits = await audits . GetPendingItems < FeatureEditEntity > ( User . Identity ) ;
131+ AuditEntity ? audit ;
132+
133+ audit = edits . SingleOrDefault ( e => e . Id == id ) ;
134+ if ( audit is null )
135+ {
136+ var ops = await audits . GetPendingItems < FeatureOpEntity > ( User . Identity ) ;
137+ audit = ops . SingleOrDefault ( e => e . Id == id ) ;
138+ }
139+
140+ if ( audit is null )
141+ {
142+ // TODO log this
143+ return BadRequest ( "Invalid ID" ) ;
144+ }
145+
146+ if ( ! audit . IsPending )
147+ {
148+ // TODO log this
149+ return BadRequest ( $ "This operation has already been audited") ;
150+ }
151+
152+ await audits . Approve ( audit , identity ) ;
153+ return Ok ( "Operation was approved successfully." ) ;
154+ }
155+
156+ [ Authorize ( "github" , Roles = $ "{ RDConstants . Roles . AdminRole } ,{ RDConstants . Roles . ReviewerRole } ") ]
157+ [ HttpPost ( "admin/audits/reject/{id}" ) ]
158+ public async Task < IActionResult > RejectPendingAudit ( [ FromRoute ] int id )
159+ {
160+ if ( User . Identity is not IIdentity identity )
161+ {
162+ // this is arguably a bug in the authentication middleware, but we can handle it gracefully here
163+ return Unauthorized ( "User identity is not available." ) ;
164+ }
165+
166+ var edits = await audits . GetPendingItems < FeatureEditEntity > ( User . Identity ) ;
167+ AuditEntity ? audit ;
168+
169+ audit = edits . SingleOrDefault ( e => e . Id == id ) ;
170+ if ( audit is null )
171+ {
172+ var ops = await audits . GetPendingItems < FeatureOpEntity > ( User . Identity ) ;
173+ audit = ops . SingleOrDefault ( e => e . Id == id ) ;
174+ }
175+
176+ if ( audit is null )
177+ {
178+ // TODO log this
179+ return BadRequest ( "Invalid ID" ) ;
180+ }
181+
182+ if ( ! audit . IsPending )
183+ {
184+ // TODO log this
185+ return BadRequest ( $ "This operation has already been audited") ;
186+ }
187+
188+ await audits . Reject ( audit , identity ) ;
189+ return Ok ( "Operation was rejected successfully." ) ;
190+ }
191+
47192#if DEBUG
48193 [ AllowAnonymous ]
49- [ EnableCors ( CorsPolicies . AllowAll ) ]
50194 [ HttpGet ( "admin/config/current" ) ]
51195 public IActionResult Config ( )
52196 {
0 commit comments