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, 2019 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 javax.servlet.http.HttpServletRequest; 39 import org.opengrok.indexer.configuration.Group; 40 import org.opengrok.indexer.configuration.Nameable; 41 import org.opengrok.indexer.configuration.Project; 42 import org.opengrok.indexer.logger.LoggerFactory; 43 44 /** 45 * This class covers authorization entities used in opengrok. 46 * 47 * Currently there are two: 48 * <ul> 49 * <li>stack of plugins</li> 50 * <li>plugin</li> 51 * </ul> 52 * 53 * The purpose is to extract common member variables and methods into an class, 54 * namely: 55 * <ul> 56 * <li>name</li> 57 * <li>role - sufficient/required/requisite</li> 58 * <li>state - working/failed</li> 59 * <li>setup - from configuration</li> 60 * </ul> 61 * and let the subclasses implement the important abstract methods. 62 * 63 * This class is intended to be read from a configuration. 64 * 65 * @author Krystof Tulinger 66 */ 67 public abstract class AuthorizationEntity implements Nameable, Serializable, Cloneable { 68 69 private static final Logger LOGGER = LoggerFactory.getLogger(AuthorizationEntity.class); 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> { 77 78 @Override test(IAuthorizationPlugin t)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 */ decision(IAuthorizationPlugin t)89 public abstract boolean decision(IAuthorizationPlugin t); 90 } 91 92 /** 93 * Predicate specialized for the 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> { 99 100 @Override test(AuthorizationEntity t)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 */ shouldSkip(AuthorizationEntity t)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<>(); 123 /** 124 * Hold current setup - merged with all ancestor's stacks. 125 */ 126 protected transient Map<String, Object> currentSetup = new TreeMap<>(); 127 128 private Set<String> forProjects = new TreeSet<>(); 129 private Set<String> forGroups = new TreeSet<>(); 130 131 protected transient boolean working = true; 132 AuthorizationEntity()133 public AuthorizationEntity() { 134 } 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 */ AuthorizationEntity(AuthorizationEntity x)147 public AuthorizationEntity(AuthorizationEntity x) { 148 flag = x.flag; 149 name = x.name; 150 setup = new TreeMap<>(x.setup); 151 working = x.working; 152 forGroups = new TreeSet<>(x.forGroups); 153 forProjects = new TreeSet<>(x.forProjects); 154 } 155 AuthorizationEntity(AuthControlFlag flag, String name)156 public AuthorizationEntity(AuthControlFlag flag, String name) { 157 this.flag = flag; 158 this.name = name; 159 } 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 */ load(Map<String, Object> parameters)168 public abstract void load(Map<String, Object> parameters); 169 170 /** 171 * Unload this entity. 172 * 173 * @see IAuthorizationPlugin#unload() 174 */ unload()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 */ isAllowed(Nameable entity, PluginDecisionPredicate pluginPredicate, PluginSkippingPredicate skippingPredicate)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 */ setPlugin(IAuthorizationPlugin plugin)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 clone()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 */ hierarchyToString(String prefix, String colorElement)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 */ getFlag()228 public AuthControlFlag getFlag() { 229 return flag; 230 } 231 232 /** 233 * Set the value of {@code flag}. 234 * 235 * @param flag new value of flag 236 */ setFlag(AuthControlFlag flag)237 public void setFlag(AuthControlFlag flag) { 238 this.flag = flag; 239 } 240 241 /** 242 * Set the value of {@code flag}. 243 * 244 * @param flag new value of flag 245 */ setFlag(String flag)246 public void setFlag(String flag) { 247 this.flag = AuthControlFlag.get(flag); 248 } 249 250 /** 251 * Get the value of {@code name}. 252 * 253 * @return the value of name 254 */ 255 @Override getName()256 public String getName() { 257 return name; 258 } 259 260 /** 261 * Set the value of {@code name}. 262 * 263 * @param name new value of name 264 */ 265 @Override setName(String name)266 public void setName(String name) { 267 this.name = name; 268 } 269 270 /** 271 * Get the value of {@code setup}. 272 * 273 * @return the value of setup 274 */ getSetup()275 public Map<String, Object> getSetup() { 276 return setup; 277 } 278 279 /** 280 * Set the value of {@code setup}. 281 * 282 * @param setup new value of setup 283 */ setSetup(Map<String, Object> setup)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 */ getCurrentSetup()293 public Map<String, Object> getCurrentSetup() { 294 return currentSetup; 295 } 296 297 /** 298 * Set the value of current setup. 299 * 300 * @param currentSetup new value of current setup 301 */ setCurrentSetup(Map<String, Object> currentSetup)302 public void setCurrentSetup(Map<String, Object> currentSetup) { 303 this.currentSetup = currentSetup; 304 } 305 306 /** 307 * Get the value of {@code forProjects}. 308 * 309 * @return the value of forProjects 310 */ forProjects()311 public Set<String> forProjects() { 312 return getForProjects(); 313 } 314 315 /** 316 * Get the value of {@code forProjects}. 317 * 318 * @return the value of forProjects 319 */ getForProjects()320 public Set<String> getForProjects() { 321 return forProjects; 322 } 323 324 /** 325 * Set the value of {@code forProjects}. 326 * 327 * @param forProjects new value of forProjects 328 */ setForProjects(Set<String> forProjects)329 public void setForProjects(Set<String> forProjects) { 330 this.forProjects = forProjects; 331 } 332 333 /** 334 * Set the value of {@code forProjects}. 335 * 336 * @param project add this project into the set 337 */ setForProjects(String project)338 public void setForProjects(String project) { 339 this.forProjects.add(project); 340 } 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 */ setForProjects(String[] projects)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 */ forGroups()360 public Set<String> forGroups() { 361 return getForGroups(); 362 } 363 364 /** 365 * Get the value of {@code forGroups}. 366 * 367 * @return the value of forGroups 368 */ getForGroups()369 public Set<String> getForGroups() { 370 return forGroups; 371 } 372 373 /** 374 * Set the value of {@code forGroups}. 375 * 376 * @param forGroups new value of forGroups 377 */ setForGroups(Set<String> forGroups)378 public void setForGroups(Set<String> forGroups) { 379 this.forGroups = forGroups; 380 } 381 382 /** 383 * Set the value of {@code forGroups}. 384 * 385 * @param group add this group into the set 386 */ setForGroups(String group)387 public void setForGroups(String group) { 388 this.forGroups.add(group); 389 } 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 */ setForGroups(String[] groups)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 */ processTargetGroupsAndProjects()419 protected void processTargetGroupsAndProjects() { 420 Set<String> groups = new TreeSet<>(); 421 422 for (String x : forGroups()) { 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) { 433 forProjects().addAll(g.getAllProjects().stream().map((t) -> t.getName()).collect(Collectors.toSet())); 434 groups.addAll(g.getRelatedGroups().stream().map((t) -> t.getName()).collect(Collectors.toSet())); 435 groups.add(x); 436 } else { 437 LOGGER.log(Level.WARNING, "Configured group \"{0}\" in forGroups section" 438 + " for name \"{1}\" does not exist", 439 new Object[]{x, getName()}); 440 } 441 } 442 setForGroups(groups); 443 444 forProjects().removeIf((t) -> { 445 /** 446 * Check the existence of the projects and issue a warning if there 447 * is no such project. 448 */ 449 Project p; 450 if ((p = Project.getByName(t)) == null) { 451 LOGGER.log(Level.WARNING, "Configured project \"{0}\" in forProjects" 452 + " section for name \"{1}\" does not exist", 453 new Object[]{t, getName()}); 454 return true; 455 } 456 return false; 457 }); 458 } 459 460 /** 461 * Check if the plugin exists and has not failed while loading. 462 * 463 * @return true if working, false otherwise 464 */ isWorking()465 public boolean isWorking() { 466 return working; 467 } 468 469 /** 470 * Mark this entity as working. 471 */ setWorking()472 public synchronized void setWorking() { 473 working = true; 474 } 475 476 /** 477 * Check if this plugin has failed during loading or is missing. 478 * 479 * This method has the same effect as !{@link isWorking()}. 480 * 481 * @return true if failed, true otherwise 482 * @see #isWorking() 483 */ isFailed()484 public boolean isFailed() { 485 return !isWorking(); 486 } 487 488 /** 489 * Set this plugin as failed. This plugin will no more call the underlying 490 * plugin isAllowed methods. 491 * 492 * @see IAuthorizationPlugin#isAllowed(HttpServletRequest, Group) 493 * @see IAuthorizationPlugin#isAllowed(HttpServletRequest, Project) 494 */ setFailed()495 public synchronized void setFailed() { 496 working = false; 497 } 498 499 /** 500 * Check if this plugin is marked as required. 501 * 502 * @return true if is required; false otherwise 503 */ isRequired()504 public boolean isRequired() { 505 return getFlag().isRequired(); 506 } 507 508 /** 509 * Check if this plugin is marked as sufficient. 510 * 511 * @return true if is sufficient; false otherwise 512 */ isSufficient()513 public boolean isSufficient() { 514 return getFlag().isSufficient(); 515 } 516 517 /** 518 * Check if this plugin is marked as requisite. 519 * 520 * @return true if is requisite; false otherwise 521 */ isRequisite()522 public boolean isRequisite() { 523 return getFlag().isRequisite(); 524 } 525 526 /** 527 * Print the entity hierarchy. 528 * 529 * @return the string containing this entity representation 530 */ hierarchyToString()531 public String hierarchyToString() { 532 return hierarchyToString("", "<span style=\"background-color: %color%;\"> </span>"); 533 } 534 535 /** 536 * Print the color element for this entity. Replace all occurrences of 537 * %color% in the input string by the current state color in the HTML HEX 538 * format. 539 * 540 * @param colorElement the string, possibly an HTML element, describing the 541 * color (can use %color%) to inject the true color of this entity state. 542 * @return the color element with filled color 543 */ colorToString(String colorElement)544 protected String colorToString(String colorElement) { 545 StringBuilder builder = new StringBuilder(colorElement.length() + 10); 546 String tmp; 547 try { 548 // #66ff33 - green 549 // #ff0000 - red 550 tmp = colorElement.replaceAll("(?<!\\\\)%color(?<!\\\\)%", isWorking() ? "#66ff33" : "#ff0000"); 551 if (tmp.isEmpty()) { 552 builder.append(" "); 553 } else { 554 builder.append(tmp); 555 } 556 } catch (PatternSyntaxException ex) { 557 builder.append(" "); 558 } 559 return builder.toString(); 560 } 561 562 /** 563 * Print the basic information about this entity. 564 * 565 * @param prefix prepend this value to each line produced 566 * @return the string containing the information. 567 */ infoToString(String prefix)568 protected String infoToString(String prefix) { 569 StringBuilder builder = new StringBuilder(40); 570 String flup = getFlag().toString().toUpperCase(Locale.ROOT); 571 String nm = getName(); 572 builder.append(" ").append(flup).append(" '").append(nm).append("'"); 573 return builder.toString(); 574 } 575 576 /** 577 * Print the setup into a string. 578 * 579 * @param prefix prepend this value to each line produced 580 * @return the string representing the entity setup 581 */ setupToString(String prefix)582 protected String setupToString(String prefix) { 583 StringBuilder builder = new StringBuilder(); 584 if (!currentSetup.isEmpty()) { 585 builder.append(prefix).append(" setup:\n"); 586 for (Entry<String, Object> entry : currentSetup.entrySet()) { 587 builder.append(prefix) 588 .append(" ") 589 .append(entry.getKey()) 590 .append(": ") 591 .append(entry.getValue()) 592 .append("\n"); 593 } 594 } 595 return builder.toString(); 596 } 597 598 /** 599 * Print the targets for groups and projects into a string. 600 * 601 * @param prefix prepend this value to each line produced 602 * @return the string representing targeted the groups and projects 603 */ targetsToString(String prefix)604 protected String targetsToString(String prefix) { 605 StringBuilder builder = new StringBuilder(); 606 if (forGroups().size() > 0) { 607 builder.append(prefix).append(" only for groups:\n"); 608 for (String x : forGroups()) { 609 builder.append(prefix).append(" ").append(x).append("\n"); 610 } 611 } 612 if (forProjects().size() > 0) { 613 builder.append(prefix).append(" only for projects:\n"); 614 for (String x : forProjects()) { 615 builder.append(prefix).append(" ").append(x).append("\n"); 616 } 617 } 618 return builder.toString(); 619 } 620 } 621