2323import java .text .SimpleDateFormat ;
2424import java .util .Arrays ;
2525import java .util .HashMap ;
26- import java .util .Iterator ;
2726import java .util .LinkedHashMap ;
2827import java .util .LinkedList ;
2928import java .util .List ;
3433import java .util .stream .Collectors ;
3534
3635import javax .jcr .RepositoryException ;
36+ import javax .jcr .Session ;
3737import javax .jcr .Value ;
3838import javax .servlet .ServletException ;
3939import javax .servlet .http .HttpServletRequest ;
4040import javax .servlet .http .HttpServletResponse ;
4141
42- import org .apache .commons .lang3 .ArrayUtils ;
4342import org .apache .commons .lang3 .StringUtils ;
4443import org .apache .felix .webconsole .WebConsoleConstants ;
45- import org .apache .jackrabbit .api .security . user . Group ;
44+ import org .apache .jackrabbit .api .JackrabbitSession ;
4645import org .apache .jackrabbit .api .security .user .User ;
4746import org .apache .jackrabbit .oak .spi .security .principal .EveryonePrincipal ;
4847import org .apache .sling .api .SlingHttpServletRequest ;
4948import org .osgi .service .component .annotations .Component ;
5049import org .osgi .service .component .annotations .Reference ;
5150import org .osgi .service .component .annotations .ReferencePolicyOption ;
51+ import org .osgi .service .metatype .annotations .AttributeDefinition ;
52+ import org .osgi .service .metatype .annotations .ObjectClassDefinition ;
5253import org .slf4j .Logger ;
5354import org .slf4j .LoggerFactory ;
5455
@@ -88,15 +89,26 @@ public class AcToolUiService {
8889 @ Reference (policyOption = ReferencePolicyOption .GREEDY )
8990 AcInstallationServiceInternal acInstallationService ;
9091
91- @ Reference (policyOption = ReferencePolicyOption .GREEDY )
92- private WebConsoleConfigTracker webConsoleConfig ;
93-
9492 @ Reference (policyOption = ReferencePolicyOption .GREEDY )
9593 private AcHistoryService acHistoryService ;
9694
95+ @ ObjectClassDefinition (name = "AC Tool UI Service" ,
96+ description ="Service that allows to apply AC Tool configuration from the UI and gather some status about the current ACLs of a system" )
97+ protected static @interface Configuration {
98+
99+ @ AttributeDefinition (name ="Allowed to read" , description ="Principal names allowed to read status about the current acls maintained in the system" )
100+ String [] allowReadPrincipalNames () default { "admin" };
101+
102+ @ AttributeDefinition (name ="Allowed to write" , description ="Principal names allowed to modify the ACLs in the system via ACTool configuration files" )
103+ String [] allowWritePrincipalNames () default { "administrators" , "admin" };
104+ }
105+
97106 private final Map <String , String > countryCodePerName ;
98107
99- public AcToolUiService () {
108+ private final Configuration config ;
109+
110+ public AcToolUiService (Configuration config ) {
111+ this .config = config ;
100112 countryCodePerName = new HashMap <>();
101113 for (String iso : Locale .getISOCountries ()) {
102114 Locale l = new Locale (Locale .ENGLISH .getLanguage (), iso );
@@ -108,16 +120,17 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp, String pa
108120 throws ServletException , IOException {
109121
110122 if (req .getRequestURI ().endsWith (SUFFIX_DUMP_YAML )) {
111- callWhenAuthorized (req , resp , this ::streamDumpToResponse );
123+ callWhenReadAccessGranted (req , resp , this ::streamDumpToResponse );
112124 } else if (req .getRequestURI ().endsWith (SUFFIX_USERS_CSV )) {
113- callWhenAuthorized (req , resp , this ::streamUsersCsvToResponse );
125+ callWhenReadAccessGranted (req , resp , this ::streamUsersCsvToResponse );
114126 } else {
127+ // everyone is allows to see the UI in general
115128 renderUi (req , resp , path , isTouchUi );
116129 }
117130 }
118131
119- private void callWhenAuthorized (HttpServletRequest req , HttpServletResponse resp , Consumer <HttpServletResponse > responseConsumer ) throws IOException {
120- if (!hasAccessToFelixWebConsole (req )) {
132+ private void callWhenReadAccessGranted (HttpServletRequest req , HttpServletResponse resp , Consumer <HttpServletResponse > responseConsumer ) throws IOException , ServletException {
133+ if (!isOneOfPrincipalNamesBound (req , config . allowReadPrincipalNames () )) {
121134 resp .sendError (HttpServletResponse .SC_FORBIDDEN , "You do not have sufficent permissions to export users/groups/permissions" );
122135 return ;
123136 }
@@ -127,12 +140,13 @@ private void callWhenAuthorized(HttpServletRequest req, HttpServletResponse resp
127140 throw e .getCause ();
128141 }
129142 }
143+
130144 @ SuppressWarnings (/* SonarCloud false positive */ {
131145 "javasecurity:S5131" /* response is sent as text/plain, it's not interpreted */ ,
132146 "javasecurity:S5145" /* logging the path is fine */ })
133147 protected void doPost (final HttpServletRequest req , final HttpServletResponse resp ) throws IOException , ServletException {
134148
135- if (!hasAccessToFelixWebConsole (req )) {
149+ if (!isOneOfPrincipalNamesBound (req , config . allowWritePrincipalNames () )) {
136150 resp .sendError (HttpServletResponse .SC_FORBIDDEN , "You do not have sufficent permissions to apply the configuration" );
137151 return ;
138152 }
@@ -157,45 +171,27 @@ protected void doPost(final HttpServletRequest req, final HttpServletResponse re
157171 }
158172
159173 /**
160- * Replicates the logic of the <a href="https://sling.apache.org/documentation/bundles/web-console-extensions.html#authentication-handling">Sling Web Console Security Provider</a>.
174+ * Similar to the logic of the <a href="https://sling.apache.org/documentation/bundles/web-console-extensions.html#authentication-handling">Sling Web Console Security Provider</a> but acting on principal names
161175 * @param req the request
162- * @return {@code true} if the user bound to the given request may also access the Felix Web Console or if we are outside of Sling, {@code false} otherwise
176+ * @param principalNames the principal names to check against
177+ * @return {@code true} if the session bound to the given request is bound to any of the given principal names
178+ * @throws ServletException
179+ * @throws RepositoryException
163180 */
164- private boolean hasAccessToFelixWebConsole (HttpServletRequest req ) {
165-
181+ private boolean isOneOfPrincipalNamesBound (HttpServletRequest req , String [] principalNames ) throws ServletException {
166182 if (!(req instanceof SlingHttpServletRequest )) {
167183 // outside Sling this is only called by the Felix Web Console, which has its own security layer
168184 LOG .debug ("Outside Sling no additional security checks are performed!" );
169185 return true ;
170186 }
187+ Session session = SlingHttpServletRequest .class .cast (req ).getResourceResolver ().adaptTo (Session .class );
188+ BoundPrincipals boundPrincipals ;
171189 try {
172- User requestUser = SlingHttpServletRequest .class .cast (req ).getResourceResolver ().adaptTo (User .class );
173- if (requestUser != null ) {
174- if (StringUtils .equals (requestUser .getID (), "admin" )) {
175- LOG .debug ("Admin user is allowed to apply AC Tool" );
176- return true ;
177- }
178-
179- if (ArrayUtils .contains (webConsoleConfig .getAllowedUsers (), requestUser .getID ())) {
180- LOG .debug ("User {} is allowed to apply AC Tool (allowed users: {})" , requestUser .getID (), ArrayUtils .toString (webConsoleConfig .getAllowedUsers ()));
181- return true ;
182- }
183-
184- Iterator <Group > memberOfIt = requestUser .memberOf ();
185-
186- while (memberOfIt .hasNext ()) {
187- Group memberOfGroup = memberOfIt .next ();
188- if (ArrayUtils .contains (webConsoleConfig .getAllowedGroups (), memberOfGroup .getID ())) {
189- LOG .debug ("Group {} is allowed to apply AC Tool (allowed groups: {})" , memberOfGroup .getID (), ArrayUtils .toString (webConsoleConfig .getAllowedGroups ()));
190- return true ;
191- }
192- }
193- }
194- LOG .debug ("Could not get associated user for Sling request" );
195- return false ;
196- } catch (Exception e ) {
197- throw new IllegalStateException ("Could not check if user may apply AC Tool configuration: " + e , e );
190+ boundPrincipals = new BoundPrincipals (JackrabbitSession .class .cast (session ));
191+ } catch (RepositoryException e ) {
192+ throw new ServletException ("Could not determine bound principals" , e );
198193 }
194+ return boundPrincipals .containsOneOf (Arrays .asList (principalNames ));
199195 }
200196
201197 public String getWebConsoleRoot (HttpServletRequest req ) {
0 commit comments