• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

oracle / opengrok / #3670

01 Nov 2023 10:10AM UTC coverage: 74.437% (-0.7%) from 75.16%
#3670

push

web-flow
Fix Sonar codesmell issues (#4460)

Signed-off-by: Gino Augustine <ginoaugustine@gmail.com>

308 of 308 new or added lines in 27 files covered. (100.0%)

43623 of 58604 relevant lines covered (74.44%)

0.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.67
/opengrok-indexer/src/main/java/org/opengrok/indexer/authorization/AuthorizationStack.java
1
/*
2
 * CDDL HEADER START
3
 *
4
 * The contents of this file are subject to the terms of the
5
 * Common Development and Distribution License (the "License").
6
 * You may not use this file except in compliance with the License.
7
 *
8
 * See LICENSE.txt included in this distribution for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing Covered Code, include this CDDL HEADER in each
12
 * file and include the License file at LICENSE.txt.
13
 * If applicable, add the following below this CDDL HEADER, with the
14
 * fields enclosed by brackets "[]" replaced with your own identifying
15
 * information: Portions Copyright [yyyy] [name of copyright owner]
16
 *
17
 * CDDL HEADER END
18
 */
19

20
/*
21
 * Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved.
22
 * Portions Copyright (c) 2018, Chris Fraire <cfraire@me.com>.
23
 */
24
package org.opengrok.indexer.authorization;
25

26
import java.util.ArrayList;
27
import java.util.List;
28
import java.util.Locale;
29
import java.util.Map;
30
import java.util.TreeMap;
31
import java.util.function.Predicate;
32
import java.util.logging.Level;
33
import java.util.logging.Logger;
34

35
import org.opengrok.indexer.configuration.Nameable;
36
import org.opengrok.indexer.logger.LoggerFactory;
37

38
/**
39
 * Subclass of {@link AuthorizationEntity}. It implements the methods to
40
 * be able to contain and making decision for:
41
 * <ul>
42
 * <li>other stacks</li>
43
 * <li>plugins</li>
44
 * </ul>
45
 *
46
 * @author Krystof Tulinger
47
 */
48
public class AuthorizationStack extends AuthorizationEntity {
49

50
    private static final long serialVersionUID = -2116160303238347415L;
51

52
    private static final Logger LOGGER = LoggerFactory.getLogger(AuthorizationStack.class);
1✔
53

54
    private List<AuthorizationEntity> stack = new ArrayList<>();
1✔
55

56
    public AuthorizationStack() {
1✔
57
    }
1✔
58

59
    /**
60
     * Copy constructor from another stack.
61
     * <ul>
62
     * <li>copy the superclass {@link AuthorizationEntity}</li>
63
     * <li>perform a deep copy of the contained stack (using
64
     * {@link AuthorizationEntity#clone()}</li>
65
     * </ul>
66
     *
67
     * @param x the stack to be copied
68
     */
69
    public AuthorizationStack(AuthorizationStack x) {
70
        super(x);
1✔
71
        stack = new ArrayList<>(x.stack.size());
1✔
72
        for (AuthorizationEntity e : x.getStack()) {
1✔
73
            stack.add(e.clone());
1✔
74
        }
1✔
75
    }
1✔
76

77
    public AuthorizationStack(AuthControlFlag flag, String name) {
78
        super(flag, name);
1✔
79
    }
1✔
80

81
    /**
82
     * Get the value of {@code stack}.
83
     *
84
     * @return the current stack
85
     */
86
    public List<AuthorizationEntity> getStack() {
87
        return stack;
1✔
88
    }
89

90
    /**
91
     * Set the value of {@code stack}.
92
     *
93
     * @param s the new stack
94
     */
95
    public void setStack(List<AuthorizationEntity> s) {
96
        this.stack = s;
×
97
    }
×
98

99
    /**
100
     * Add a new authorization entity into this stack.
101
     *
102
     * @param s new entity
103
     */
104
    public void add(AuthorizationEntity s) {
105
        this.stack.add(s);
1✔
106
    }
1✔
107

108
    /**
109
     * Remove the given authorization entity from this stack.
110
     *
111
     * @param s the entity to remove
112
     */
113
    public void remove(AuthorizationEntity s) {
114
        s.unload();
×
115
        this.stack.remove(s);
×
116
    }
×
117

118
    /**
119
     * Load all authorization entities in this stack.
120
     * <p>
121
     * <p>If the method is unable to load any of the entities contained in this
122
     * stack then this stack is marked as failed. Note that it does not affect
123
     * the authorization decision made by this stack.
124
     *
125
     * @param parameters parameters given in the configuration
126
     *
127
     * @see IAuthorizationPlugin#load(java.util.Map)
128
     */
129
    @Override
130
    public void load(Map<String, Object> parameters) {
131
        setCurrentSetup(new TreeMap<>());
1✔
132
        getCurrentSetup().putAll(parameters);
1✔
133
        getCurrentSetup().putAll(getSetup());
1✔
134

135
        LOGGER.log(Level.INFO, "[{0}] Stack \"{1}\" is loading.",
1✔
136
                new Object[]{getFlag().toString().toUpperCase(Locale.ROOT),
1✔
137
                getName()});
1✔
138

139
        // fill properly the "forGroups" and "forProjects" fields
140
        processTargetGroupsAndProjects();
1✔
141

142
        setWorking();
1✔
143

144
        int cnt = 0;
1✔
145
        for (AuthorizationEntity authEntity : getStack()) {
1✔
146
            authEntity.load(getCurrentSetup());
1✔
147
            if (authEntity.isWorking()) {
1✔
148
                cnt++;
1✔
149
            }
150
        }
1✔
151

152
        if (!getStack().isEmpty() && cnt < getStack().size()) {
1✔
153
            setFailed();
1✔
154
        }
155

156
        LOGGER.log(Level.INFO, "[{0}] Stack \"{1}\" is {2}.",
1✔
157
                new Object[]{
158
                    getFlag().toString().toUpperCase(Locale.ROOT),
1✔
159
                    getName(),
1✔
160
                    isWorking() ? "ready" : "not fully ok"});
1✔
161
    }
1✔
162

163
    /**
164
     * Unload all plugins contained in this stack.
165
     *
166
     * @see IAuthorizationPlugin#unload()
167
     */
168
    @Override
169
    public void unload() {
170
        for (AuthorizationEntity plugin : getStack()) {
1✔
171
            plugin.unload();
1✔
172
        }
1✔
173
    }
1✔
174

175
    /**
176
     * Test the given entity if it should be allowed with in this stack context
177
     * if and only if the stack is not marked as failed.
178
     *
179
     * @param entity the given entity - this is either group or project and is
180
     * passed just for the logging purposes.
181
     * @param pluginPredicate predicate returning true or false for the given
182
     * entity which determines if the authorization for such entity is
183
     * successful or failed for particular request and plugin
184
     * @param skippingPredicate predicate returning true if this authorization
185
     * entity should be omitted from the authorization process
186
     * @return true if successful; false otherwise
187
     */
188
    @Override
189
    public boolean isAllowed(Nameable entity,
190
            PluginDecisionPredicate pluginPredicate,
191
            PluginSkippingPredicate skippingPredicate) {
192
        boolean overallDecision = true;
1✔
193
        LOGGER.log(Level.FINER, "Authorization for \"{0}\" in \"{1}\" [{2}]",
1✔
194
                new Object[]{entity.getName(), this.getName(), this.getFlag()});
1✔
195

196
        if (skippingPredicate.shouldSkip(this)) {
1✔
197
            LOGGER.log(Level.FINER, "AuthEntity \"{0}\" [{1}] skipping testing of name \"{2}\"",
×
198
                    new Object[]{this.getName(), this.getFlag(), entity.getName()});
×
199
        } else {
200
            overallDecision = processStack(entity, pluginPredicate, skippingPredicate);
1✔
201
        }
202

203
        LOGGER.log(Level.FINER, "Authorization for \"{0}\" in \"{1}\" [{2}] => {3}",
1✔
204
                new Object[]{entity.getName(), this.getName(), this.getFlag(), overallDecision ? "true" : "false"});
1✔
205
        return overallDecision;
1✔
206
    }
207

208
    /**
209
     * Process the stack.
210
     *
211
     * @param entity the given entity
212
     * @param pluginPredicate predicate returning true or false for the given
213
     * entity which determines if the authorization for such entity is
214
     * successful or failed for particular request and plugin
215
     * @param skippingPredicate predicate returning true if this authorization
216
     * entity should be omitted from the authorization process
217
     * @return true if entity is allowed; false otherwise
218
     */
219
    protected boolean processStack(Nameable entity,
220
            PluginDecisionPredicate pluginPredicate,
221
            PluginSkippingPredicate skippingPredicate) {
222

223
        boolean overallDecision = true;
1✔
224
        boolean optionalFailure = false;
1✔
225

226
        if (getStack().isEmpty()) {
1✔
227
            return true;
1✔
228
        }
229

230
        for (AuthorizationEntity authEntity : getStack()) {
1✔
231

232
            if (skippingPredicate.shouldSkip(authEntity)) {
1✔
233
                LOGGER.log(Level.FINEST, "AuthEntity \"{0}\" [{1}] skipping testing of name \"{2}\"",
×
234
                        new Object[]{authEntity.getName(), authEntity.getFlag(), entity.getName()});
×
235
                continue;
×
236
            }
237
            // run the plugin's test method
238
            try {
239
                LOGGER.log(Level.FINEST, "AuthEntity \"{0}\" [{1}] testing a name \"{2}\"",
1✔
240
                        new Object[]{authEntity.getName(), authEntity.getFlag(), entity.getName()});
1✔
241

242
                boolean entityDecision = authEntity.isAllowed(entity, pluginPredicate, skippingPredicate);
1✔
243

244
                LOGGER.log(Level.FINEST, "AuthEntity \"{0}\" [{1}] testing a name \"{2}\" => {3}",
1✔
245
                        new Object[]{authEntity.getName(), authEntity.getFlag(), entity.getName(),
1✔
246
                                entityDecision ? "true" : "false"});
1✔
247

248
                if (!entityDecision && authEntity.isRequired()) {
1✔
249
                    // required sets a failure but still invokes all other plugins
250
                    overallDecision = false;
1✔
251
                } else if (!entityDecision && authEntity.isRequisite()) {
1✔
252
                    // requisite sets a failure and immediately returns the failure
253
                    overallDecision = false;
1✔
254
                    break;
1✔
255
                } else if (!entityDecision && authEntity.isOptional()) {
1✔
256
                    optionalFailure = true;
1✔
257
                } else if (overallDecision && entityDecision && authEntity.isSufficient()) {
1✔
258
                    // sufficient immediately returns the success
259
                    break;
1✔
260
                }
261
            } catch (AuthorizationException ex) {
×
262
                // Propagate up so that proper HTTP error can be given.
263
                LOGGER.log(Level.FINEST, () -> "got authorization exception: " + ex.getMessage());
×
264
                throw ex;
×
265
            } catch (Throwable ex) {
1✔
266
                LOGGER.log(Level.WARNING,
1✔
267
                        String.format("AuthEntity \"%s\" has failed the testing of \"%s\" with an exception.",
1✔
268
                                authEntity.getName(),
1✔
269
                                entity.getName()),
1✔
270
                        ex);
271

272
                LOGGER.log(Level.FINEST, "AuthEntity \"{0}\" [{1}] testing a name \"{2}\" => {3}",
1✔
273
                        new Object[]{authEntity.getName(), authEntity.getFlag(), entity.getName(),
1✔
274
                                "false (failed)"});
275

276
                if (authEntity.isOptional()) {
1✔
277
                    optionalFailure = true;
1✔
278
                    continue;
1✔
279
                }
280
                // set the return value to false for this faulty plugin
281
                if (!authEntity.isSufficient()) {
1✔
282
                    overallDecision = false;
1✔
283
                }
284
                // requisite plugin may immediately return the failure
285
                if (authEntity.isRequisite()) {
1✔
286
                    break;
×
287
                }
288
            }
1✔
289
        }
1✔
290

291
        if (optionalFailure &&
1✔
292
                getStack().stream().filter(AuthorizationEntity::isOptional).count() == 1 &&
1✔
293
                getStack().stream().filter(Predicate.not(AuthorizationEntity::isOptional)).findAny().isEmpty()) {
1✔
294
            return false;
1✔
295
        }
296

297
        return overallDecision;
1✔
298
    }
299

300
    /**
301
     * Set the plugin to all classes in this stack which requires this class in
302
     * the configuration. This creates a new instance of the plugin for each
303
     * class which needs it.
304
     * <p>
305
     * <p>This is where the loaded plugin classes get to be a part of the
306
     * authorization process. When the {@link AuthorizationPlugin} does not get
307
     * its {@link IAuthorizationPlugin} it is marked as failed and returns false
308
     * to all authorization decisions.
309
     *
310
     * @param plugin the new instance of a plugin
311
     * @return true if there is such case; false otherwise
312
     */
313
    @Override
314
    public boolean setPlugin(IAuthorizationPlugin plugin) {
315
        boolean ret = false;
1✔
316
        for (AuthorizationEntity p : getStack()) {
1✔
317
            ret = p.setPlugin(plugin) || ret;
1✔
318
        }
1✔
319
        return ret;
1✔
320
    }
321

322
    /**
323
     * Clones the stack. Performs:
324
     * <ul>
325
     * <li>copy the superclass {@link AuthorizationEntity}</li>
326
     * <li>perform a deep copy of the contained stack</li>
327
     * </ul>
328
     *
329
     * @return new instance of {@link AuthorizationStack}
330
     */
331
    @Override
332
    public AuthorizationStack clone() {
333
        return new AuthorizationStack(this);
1✔
334
    }
335

336
    /**
337
     * Print the stack hierarchy. Process also all contained plugins and
338
     * substacks.
339
     *
340
     * @param prefix this prefix should be prepended to every line produced by
341
     * this stack
342
     * @param colorElement a possible element where any occurrence of %color%
343
     * will be replaced with a HTML HEX color representing this entity state.
344
     * @return the string containing this stack representation
345
     */
346
    @Override
347
    public String hierarchyToString(String prefix, String colorElement) {
348
        StringBuilder builder = new StringBuilder(prefix);
1✔
349

350
        builder.append(colorToString(colorElement));
1✔
351
        builder.append(infoToString(prefix));
1✔
352
        builder.append(" (stack ").append(isWorking() ? "ok" : "not fully ok").append(")");
1✔
353
        builder.append("\n");
1✔
354

355
        builder.append(setupToString(prefix));
1✔
356
        builder.append(targetsToString(prefix));
1✔
357

358
        for (AuthorizationEntity authEntity : getStack()) {
1✔
359
            builder.append(authEntity.hierarchyToString(prefix + "    ", colorElement));
×
360
        }
×
361
        return builder.toString();
1✔
362
    }
363
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc