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

oracle / opengrok / #3652

25 Oct 2023 08:13AM UTC coverage: 75.792% (+0.02%) from 75.777%
#3652

push

web-flow
Sonar Code Smell issue fixes (#4455)


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

23 of 23 new or added lines in 7 files covered. (100.0%)

44399 of 58580 relevant lines covered (75.79%)

0.76 hits per line

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

75.41
/opengrok-indexer/src/main/java/org/opengrok/indexer/authorization/AuthorizationEntity.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, 2021, 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.io.Serializable;
27
import java.util.Locale;
28
import java.util.Map;
29
import java.util.Map.Entry;
30
import java.util.Set;
31
import java.util.TreeMap;
32
import java.util.TreeSet;
33
import java.util.function.Predicate;
34
import java.util.logging.Level;
35
import java.util.logging.Logger;
36
import java.util.regex.PatternSyntaxException;
37
import java.util.stream.Collectors;
38
import org.opengrok.indexer.configuration.Group;
39
import org.opengrok.indexer.configuration.Nameable;
40
import org.opengrok.indexer.configuration.Project;
41
import org.opengrok.indexer.logger.LoggerFactory;
42

43
/**
44
 * This class covers authorization entities used in opengrok.
45
 *
46
 * Currently there are two:
47
 * <ul>
48
 * <li>stack of plugins</li>
49
 * <li>plugin</li>
50
 * </ul>
51
 *
52
 * The purpose is to extract common member variables and methods into an class,
53
 * namely:
54
 * <ul>
55
 * <li>name</li>
56
 * <li>role - sufficient/required/requisite</li>
57
 * <li>state - working/failed</li>
58
 * <li>setup - from configuration</li>
59
 * </ul>
60
 * and let the subclasses implement the important abstract methods.
61
 *
62
 * This class is intended to be read from a configuration.
63
 *
64
 * @author Krystof Tulinger
65
 */
66
public abstract class AuthorizationEntity implements Nameable, Serializable, Cloneable {
67

68
    private static final Logger LOGGER = LoggerFactory.getLogger(AuthorizationEntity.class);
1✔
69
    private static final String BLANK_SPACE = "          ";
70

71
    /**
72
     * Predicate specialized for the the plugin decisions. The caller should
73
     * implement the <code>decision</code> method. Returning true if the plugin
74
     * allows the action or false when the plugin forbids the action.
75
     */
76
    public abstract static class PluginDecisionPredicate implements Predicate<IAuthorizationPlugin> {
1✔
77

78
        @Override
79
        public boolean test(IAuthorizationPlugin t) {
80
            return decision(t);
×
81
        }
82

83
        /**
84
         * Perform the authorization check for this plugin.
85
         *
86
         * @param t the plugin
87
         * @return true if plugin allows the action; false otherwise
88
         */
89
        public abstract boolean decision(IAuthorizationPlugin t);
90
    }
91

92
    /**
93
     * Predicate specialized for the entity skipping decisions. The caller
94
     * should implement the <code>shouldSkip</code> method. Returning true if
95
     * the entity should be skipped for this action and false if the entity
96
     * should be used.
97
     */
98
    public abstract static class PluginSkippingPredicate implements Predicate<AuthorizationEntity> {
1✔
99

100
        @Override
101
        public boolean test(AuthorizationEntity t) {
102
            return shouldSkip(t);
×
103
        }
104

105
        /**
106
         * Decide if the entity should be skipped in this step of authorization.
107
         *
108
         * @param t the entity
109
         * @return true if skipped (authorization decision will not be affected
110
         * by this entity) or false if it should be used (authorization decision
111
         * will be affected by this entity)
112
         */
113
        public abstract boolean shouldSkip(AuthorizationEntity t);
114
    }
115

116
    private static final long serialVersionUID = 1L;
117
    /**
118
     * One of "required", "requisite", "sufficient".
119
     */
120
    protected AuthControlFlag flag;
121
    protected String name;
122
    protected Map<String, Object> setup = new TreeMap<>();
1✔
123
    /**
124
     * Hold current setup - merged with all ancestor's stacks.
125
     */
126
    protected transient Map<String, Object> currentSetup = new TreeMap<>();
1✔
127

128
    private Set<String> forProjects = new TreeSet<>();
1✔
129
    private Set<String> forGroups = new TreeSet<>();
1✔
130

131
    protected transient boolean working = true;
1✔
132

133
    protected AuthorizationEntity() {
1✔
134
    }
1✔
135

136
    /**
137
     * Copy constructor for the entity.
138
     * <ul>
139
     * <li>copy flag</li>
140
     * <li>copy name</li>
141
     * <li>deep copy of the setup</li>
142
     * <li>copy the working attribute</li>
143
     * </ul>
144
     *
145
     * @param x the entity to be copied
146
     */
147
    protected AuthorizationEntity(AuthorizationEntity x) {
1✔
148
        flag = x.flag;
1✔
149
        name = x.name;
1✔
150
        setup = new TreeMap<>(x.setup);
1✔
151
        working = x.working;
1✔
152
        forGroups = new TreeSet<>(x.forGroups);
1✔
153
        forProjects = new TreeSet<>(x.forProjects);
1✔
154
    }
1✔
155

156
    protected AuthorizationEntity(AuthControlFlag flag, String name) {
1✔
157
        this.flag = flag;
1✔
158
        this.name = name;
1✔
159
    }
1✔
160

161
    /**
162
     * Load this entity with given parameters.
163
     *
164
     * @param parameters given parameters passed to the plugin's load method
165
     *
166
     * @see IAuthorizationPlugin#load(java.util.Map)
167
     */
168
    public abstract void load(Map<String, Object> parameters);
169

170
    /**
171
     * Unload this entity.
172
     *
173
     * @see IAuthorizationPlugin#unload()
174
     */
175
    public abstract void unload();
176

177
    /**
178
     * Test the given entity if it should be allowed with this authorization
179
     * check.
180
     *
181
     * @param entity the given entity - this is either group or project and is
182
     * passed just for the logging purposes.
183
     * @param pluginPredicate predicate returning true or false for the given
184
     * entity which determines if the authorization for such entity is
185
     * successful or failed
186
     * @param skippingPredicate predicate returning true if this authorization
187
     * entity should be omitted from the authorization process
188
     * @return true if successful; false otherwise
189
     */
190
    public abstract boolean isAllowed(Nameable entity,
191
            PluginDecisionPredicate pluginPredicate,
192
            PluginSkippingPredicate skippingPredicate);
193

194
    /**
195
     * Set the plugin to all classes which requires this class in the
196
     * configuration. This creates a new instance of the plugin for each class
197
     * which needs it.
198
     *
199
     * @param plugin the new instance of a plugin
200
     * @return true if there is such case; false otherwise
201
     */
202
    public abstract boolean setPlugin(IAuthorizationPlugin plugin);
203

204
    /**
205
     * Perform a deep copy of the entity.
206
     *
207
     * @return the new instance of this entity
208
     */
209
    @Override
210
    public abstract AuthorizationEntity clone();
211

212
    /**
213
     * Print the entity hierarchy.
214
     *
215
     * @param prefix this prefix should be prepended to every line produced by
216
     * this entity
217
     * @param colorElement a possible element where any occurrence of %color%
218
     * will be replaced with a HTML HEX color representing this entity state.
219
     * @return the string containing this entity representation
220
     */
221
    public abstract String hierarchyToString(String prefix, String colorElement);
222

223
    /**
224
     * Get the value of {@code flag}.
225
     *
226
     * @return the value of flag
227
     */
228
    public AuthControlFlag getFlag() {
229
        return flag;
1✔
230
    }
231

232
    /**
233
     * Set the value of {@code flag}.
234
     *
235
     * @param flag new value of flag
236
     */
237
    public void setFlag(AuthControlFlag flag) {
238
        this.flag = flag;
1✔
239
    }
1✔
240

241
    /**
242
     * Set the value of {@code flag}.
243
     *
244
     * @param flag new value of flag
245
     */
246
    public void setFlag(String flag) {
247
        this.flag = AuthControlFlag.get(flag);
1✔
248
    }
1✔
249

250
    /**
251
     * Get the value of {@code name}.
252
     *
253
     * @return the value of name
254
     */
255
    @Override
256
    public String getName() {
257
        return name;
1✔
258
    }
259

260
    /**
261
     * Set the value of {@code name}.
262
     *
263
     * @param name new value of name
264
     */
265
    @Override
266
    public void setName(String name) {
267
        this.name = name;
1✔
268
    }
1✔
269

270
    /**
271
     * Get the value of {@code setup}.
272
     *
273
     * @return the value of setup
274
     */
275
    public Map<String, Object> getSetup() {
276
        return setup;
1✔
277
    }
278

279
    /**
280
     * Set the value of {@code setup}.
281
     *
282
     * @param setup new value of setup
283
     */
284
    public void setSetup(Map<String, Object> setup) {
285
        this.setup = setup;
×
286
    }
×
287

288
    /**
289
     * Get the value of current setup.
290
     *
291
     * @return the value of current setup
292
     */
293
    public Map<String, Object> getCurrentSetup() {
294
        return currentSetup;
1✔
295
    }
296

297
    /**
298
     * Set the value of current setup.
299
     *
300
     * @param currentSetup new value of current setup
301
     */
302
    public void setCurrentSetup(Map<String, Object> currentSetup) {
303
        this.currentSetup = currentSetup;
1✔
304
    }
1✔
305

306
    /**
307
     * Get the value of {@code forProjects}.
308
     *
309
     * @return the value of forProjects
310
     */
311
    public Set<String> forProjects() {
312
        return getForProjects();
1✔
313
    }
314

315
    /**
316
     * Get the value of {@code forProjects}.
317
     *
318
     * @return the value of forProjects
319
     */
320
    public Set<String> getForProjects() {
321
        return forProjects;
1✔
322
    }
323

324
    /**
325
     * Set the value of {@code forProjects}.
326
     *
327
     * @param forProjects new value of forProjects
328
     */
329
    public void setForProjects(Set<String> forProjects) {
330
        this.forProjects = forProjects;
1✔
331
    }
1✔
332

333
    /**
334
     * Set the value of {@code forProjects}.
335
     *
336
     * @param project add this project into the set
337
     */
338
    public void setForProjects(String project) {
339
        this.forProjects.add(project);
1✔
340
    }
1✔
341

342
    /**
343
     * Set the value of {@code forProjects}.
344
     *
345
     * @param projects add all projects in this array into the set
346
     *
347
     * @see #setForProjects(java.lang.String)
348
     */
349
    public void setForProjects(String[] projects) {
350
        for (String project : projects) {
×
351
            setForProjects(project);
×
352
        }
353
    }
×
354

355
    /**
356
     * Get the value of {@code forGroups}.
357
     *
358
     * @return the value of forGroups
359
     */
360
    public Set<String> forGroups() {
361
        return getForGroups();
1✔
362
    }
363

364
    /**
365
     * Get the value of {@code forGroups}.
366
     *
367
     * @return the value of forGroups
368
     */
369
    public Set<String> getForGroups() {
370
        return forGroups;
1✔
371
    }
372

373
    /**
374
     * Set the value of {@code forGroups}.
375
     *
376
     * @param forGroups new value of forGroups
377
     */
378
    public void setForGroups(Set<String> forGroups) {
379
        this.forGroups = forGroups;
1✔
380
    }
1✔
381

382
    /**
383
     * Set the value of {@code forGroups}.
384
     *
385
     * @param group add this group into the set
386
     */
387
    public void setForGroups(String group) {
388
        this.forGroups.add(group);
1✔
389
    }
1✔
390

391
    /**
392
     * Set the value of {@code forGroups}.
393
     *
394
     * @param groups add all groups in this array into the set
395
     *
396
     * @see #setForGroups(java.lang.String)
397
     */
398
    public void setForGroups(String[] groups) {
399
        for (String group : groups) {
×
400
            setForGroups(group);
×
401
        }
402
    }
×
403

404
    /**
405
     * Discover all targeted groups and projects for every group given by
406
     * {@link #forGroups()}.
407
     *
408
     * <ul>
409
     * <li>add to the {@link #forGroups()} all groups which are descendant
410
     * groups to the group</li>
411
     * <li>add to the {@link #forGroups()} all groups which are parent groups to
412
     * the group</li>
413
     * <li>add to the {@link #forProjects()} all projects and repositories which
414
     * are in the descendant groups or in the group itself</li>
415
     * <li>issue a warning for non-existent groups</li>
416
     * <li>issue a warning for non-existent projects</li>
417
     * </ul>
418
     */
419
    protected void processTargetGroupsAndProjects() {
420
        Set<String> groups = new TreeSet<>();
1✔
421

422
        for (String x : forGroups()) {
1✔
423
            /**
424
             * Full group discovery takes place here. All projects/repositories
425
             * in the group are added into "forProjects" and all subgroups
426
             * (including projects/repositories) and parent groups (excluding
427
             * the projects/repositories) are added into "forGroups".
428
             *
429
             * If the group does not exist then a warning is issued.
430
             */
431
            Group g;
432
            if ((g = Group.getByName(x)) != null) {
1✔
433
                forProjects().addAll(g.getAllProjects().stream().map(Project::getName).collect(Collectors.toSet()));
1✔
434
                groups.addAll(g.getRelatedGroups().stream().map(Group::getName).collect(Collectors.toSet()));
1✔
435
                groups.add(x);
1✔
436
            } else {
437
                LOGGER.log(Level.WARNING, "Configured group \"{0}\" in forGroups section"
1✔
438
                        + " for name \"{1}\" does not exist",
439
                        new Object[]{x, getName()});
1✔
440
            }
441
        }
1✔
442
        setForGroups(groups);
1✔
443

444
        forProjects().removeIf(t -> {
1✔
445
            /**
446
             * Check the existence of the projects and issue a warning if there
447
             * is no such project.
448
             */
449
            if ( Project.getByName(t) == null) {
1✔
450
                LOGGER.log(Level.WARNING, "Configured project \"{0}\" in forProjects"
1✔
451
                        + " section for name \"{1}\" does not exist",
452
                        new Object[]{t, getName()});
1✔
453
                return true;
1✔
454
            }
455
            return false;
1✔
456
        });
457
    }
1✔
458

459
    /**
460
     * Check if the plugin exists and has not failed while loading.
461
     *
462
     * @return true if working, false otherwise
463
     */
464
    public boolean isWorking() {
465
        return working;
1✔
466
    }
467

468
    /**
469
     * Mark this entity as working.
470
     */
471
    public synchronized void setWorking() {
472
        working = true;
1✔
473
    }
1✔
474

475
    /**
476
     * Check if this plugin has failed during loading or is missing.
477
     *
478
     * This method has the same effect as !{@link isWorking()}.
479
     *
480
     * @return true if failed, true otherwise
481
     * @see #isWorking()
482
     */
483
    public boolean isFailed() {
484
        return !isWorking();
1✔
485
    }
486

487
    /**
488
     * Set this plugin as failed. This plugin will no more call the underlying
489
     * plugin isAllowed methods.
490
     *
491
     * @see IAuthorizationPlugin#isAllowed(jakarta.servlet.http.HttpServletRequest, Group)
492
     * @see IAuthorizationPlugin#isAllowed(jakarta.servlet.http.HttpServletRequest, Project)
493
     */
494
    public synchronized void setFailed() {
495
        working = false;
1✔
496
    }
1✔
497

498
    /**
499
     * Check if this plugin is marked as required.
500
     *
501
     * @return true if is required; false otherwise
502
     */
503
    public boolean isRequired() {
504
        return getFlag().isRequired();
1✔
505
    }
506

507
    /**
508
     * Check if this plugin is marked as sufficient.
509
     *
510
     * @return true if is sufficient; false otherwise
511
     */
512
    public boolean isSufficient() {
513
        return getFlag().isSufficient();
1✔
514
    }
515

516
    /**
517
     * Check if this plugin is marked as requisite.
518
     *
519
     * @return true if is requisite; false otherwise
520
     */
521
    public boolean isRequisite() {
522
        return getFlag().isRequisite();
1✔
523
    }
524

525
    /**
526
     * Check if this plugin is marked as optional.
527
     *
528
     * @return true if is optional; false otherwise
529
     */
530
    public boolean isOptional() {
531
        return getFlag().isOptional();
1✔
532
    }
533

534
    /**
535
     * Print the entity hierarchy.
536
     *
537
     * @return the string containing this entity representation
538
     */
539
    public String hierarchyToString() {
540
        return hierarchyToString("", "<span style=\"background-color: %color%;\"> </span>");
1✔
541
    }
542

543
    /**
544
     * Print the color element for this entity. Replace all occurrences of
545
     * %color% in the input string by the current state color in the HTML HEX
546
     * format.
547
     *
548
     * @param colorElement the string, possibly an HTML element, describing the
549
     * color (can use %color%) to inject the true color of this entity state.
550
     * @return the color element with filled color
551
     */
552
    protected String colorToString(String colorElement) {
553
        StringBuilder builder = new StringBuilder(colorElement.length() + 10);
1✔
554
        String tmp;
555
        try {
556
            // #66ff33 - green
557
            // #ff0000 - red
558
            tmp = colorElement.replaceAll("(?<!\\\\)%color(?<!\\\\)%", isWorking() ? "#66ff33" : "#ff0000");
1✔
559
            if (tmp.isEmpty()) {
1✔
560
                builder.append(" ");
×
561
            } else {
562
                builder.append(tmp);
1✔
563
            }
564
        } catch (PatternSyntaxException ex) {
×
565
            builder.append(" ");
×
566
        }
1✔
567
        return builder.toString();
1✔
568
    }
569

570
    /**
571
     * Print the basic information about this entity.
572
     *
573
     * @param prefix prepend this value to each line produced
574
     * @return the string containing the information.
575
     */
576
    protected String infoToString(String prefix) {
577
        StringBuilder builder = new StringBuilder(40);
1✔
578
        String flup = getFlag().toString().toUpperCase(Locale.ROOT);
1✔
579
        String nm = getName();
1✔
580
        builder.append(" ").append(flup).append(" '").append(nm).append("'");
1✔
581
        return builder.toString();
1✔
582
    }
583

584
    /**
585
     * Print the setup into a string.
586
     *
587
     * @param prefix prepend this value to each line produced
588
     * @return the string representing the entity setup
589
     */
590
    protected String setupToString(String prefix) {
591
        StringBuilder builder = new StringBuilder();
1✔
592
        if (!currentSetup.isEmpty()) {
1✔
593
            builder.append(prefix).append("      setup:\n");
×
594
            for (Entry<String, Object> entry : currentSetup.entrySet()) {
×
595
                builder.append(prefix)
×
596
                        .append(BLANK_SPACE)
×
597
                        .append(entry.getKey())
×
598
                        .append(": ")
×
599
                        .append(entry.getValue())
×
600
                        .append("\n");
×
601
            }
×
602
        }
603
        return builder.toString();
1✔
604
    }
605

606
    /**
607
     * Print the targets for groups and projects into a string.
608
     *
609
     * @param prefix prepend this value to each line produced
610
     * @return the string representing targeted the groups and projects
611
     */
612
    protected String targetsToString(String prefix) {
613
        StringBuilder builder = new StringBuilder();
1✔
614
        if (!forGroups().isEmpty()) {
1✔
615
            builder.append(prefix).append("      only for groups:\n");
×
616
            for (String x : forGroups()) {
×
617
                builder.append(prefix).append(BLANK_SPACE).append(x).append("\n");
×
618
            }
×
619
        }
620
        if (!forProjects().isEmpty()) {
1✔
621
            builder.append(prefix).append("      only for projects:\n");
×
622
            for (String x : forProjects()) {
×
623
                builder.append(prefix).append(BLANK_SPACE).append(x).append("\n");
×
624
            }
×
625
        }
626
        return builder.toString();
1✔
627
    }
628
}
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