2
2
using Microsoft . AspNetCore . Cors ;
3
3
using Microsoft . AspNetCore . Mvc ;
4
4
using Microsoft . Extensions . Options ;
5
+ using rubberduckvba . Server . Model . Entity ;
5
6
using rubberduckvba . Server . Services ;
7
+ using System . Security . Principal ;
6
8
7
9
namespace rubberduckvba . Server . Api . Admin ;
8
10
9
11
[ 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
11
14
{
12
15
/// <summary>
13
16
/// Enqueues a job that updates xmldoc content from the latest release/pre-release tags.
14
17
/// </summary>
15
18
/// <returns>The unique identifier of the enqueued job.</returns>
16
- [ Authorize ( "github" ) ]
17
- [ EnableCors ( CorsPolicies . AllowAuthenticated ) ]
19
+ [ Authorize ( "github" , Roles = RDConstants . Roles . AdminRole ) ]
18
20
[ HttpPost ( "admin/update/xmldoc" ) ]
19
21
public IActionResult UpdateXmldocContent ( )
20
22
{
@@ -26,27 +28,169 @@ public IActionResult UpdateXmldocContent()
26
28
/// Enqueues a job that gets the latest release/pre-release tags and their respective assets, and updates the installer download stats.
27
29
/// </summary>
28
30
/// <returns>The unique identifier of the enqueued job.</returns>
29
- [ Authorize ( "github" ) ]
30
- [ EnableCors ( CorsPolicies . AllowAuthenticated ) ]
31
+ [ Authorize ( "github" , Roles = RDConstants . Roles . AdminRole ) ]
31
32
[ HttpPost ( "admin/update/tags" ) ]
32
33
public IActionResult UpdateTagMetadata ( )
33
34
{
34
35
var jobId = hangfire . UpdateTagMetadata ( ) ;
35
36
return Ok ( jobId ) ;
36
37
}
37
38
38
- [ Authorize ( "github" ) ]
39
- [ EnableCors ( CorsPolicies . AllowAuthenticated ) ]
39
+ [ Authorize ( "github" , Roles = RDConstants . Roles . AdminRole ) ]
40
40
[ HttpPost ( "admin/cache/clear" ) ]
41
41
public IActionResult ClearCache ( )
42
42
{
43
43
cache . Clear ( ) ;
44
44
return Ok ( ) ;
45
45
}
46
46
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
+
47
192
#if DEBUG
48
193
[ AllowAnonymous ]
49
- [ EnableCors ( CorsPolicies . AllowAll ) ]
50
194
[ HttpGet ( "admin/config/current" ) ]
51
195
public IActionResult Config ( )
52
196
{
0 commit comments