001/* 002 * (C) Copyright 2006-2018 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Bogdan Stefanescu 018 * Florent Guillaume 019 */ 020package org.nuxeo.ecm.core.api.security; 021 022import java.io.Serializable; 023import java.util.Calendar; 024import java.util.GregorianCalendar; 025import java.util.HashMap; 026import java.util.Map; 027import java.util.Objects; 028import java.util.regex.Matcher; 029import java.util.regex.Pattern; 030 031import org.apache.commons.lang3.StringUtils; 032 033/** 034 * Access control entry, assigning a permission to a user. 035 * <p> 036 * Optionally, the assignment can be denied instead of being granted. 037 */ 038public final class ACE implements Serializable, Cloneable { 039 040 private static final long serialVersionUID = 1L; 041 042 public enum Status { 043 PENDING, EFFECTIVE, ARCHIVED 044 } 045 046 /** 047 * An ACE that blocks all permissions for everyone. 048 * 049 * @since 6.0 050 */ 051 public static final ACE BLOCK = new ACE(SecurityConstants.EVERYONE, SecurityConstants.EVERYTHING, false); 052 053 private final String username; 054 055 private final String permission; 056 057 private final boolean isGranted; 058 059 private Calendar begin; 060 061 private Calendar end; 062 063 private String creator; 064 065 private Map<String, Serializable> contextData = new HashMap<>(); 066 067 protected static final Pattern ID_PATTERN = Pattern.compile("^(.+):([^:]+):([^:]+):([^:]*):([^:]*):([^:]*)$"); 068 069 /** 070 * Create an ACE from an id. 071 * 072 * @since 7.4 073 */ 074 public static ACE fromId(String aceId) { 075 if (aceId == null) { 076 return null; 077 } 078 079 // An ACE is composed of tokens separated with ":" caracter 080 // First 3 tokens are mandatory; following 3 tokens are optional 081 // The ":" separator is still present even if the tokens are empty 082 // Example: jsmith:ReadWrite:true::: 083 // The first token (username) is allowed to contain embedded ":". 084 Matcher m = ID_PATTERN.matcher(aceId); 085 if (!m.matches()) { 086 throw new IllegalArgumentException(String.format("Invalid ACE id: %s", aceId)); 087 } 088 089 String[] parts = new String[m.groupCount()]; 090 for (int i = 1; i <= m.groupCount(); i++) { 091 parts[i - 1] = m.group(i); 092 } 093 094 String username = parts[0]; 095 String permission = parts[1]; 096 boolean isGranted = Boolean.parseBoolean(parts[2]); 097 098 ACEBuilder builder = ACE.builder(username, permission).isGranted(isGranted); 099 100 if (parts.length >= 4 && StringUtils.isNotBlank(parts[3])) { 101 builder.creator(parts[3]); 102 } 103 104 if (parts.length >= 5 && StringUtils.isNotBlank(parts[4])) { 105 Calendar begin = new GregorianCalendar(); 106 begin.setTimeInMillis(Long.parseLong(parts[4])); 107 builder.begin(begin); 108 } 109 110 if (parts.length >= 6 && StringUtils.isNotBlank(parts[5])) { 111 Calendar end = new GregorianCalendar(); 112 end.setTimeInMillis(Long.parseLong(parts[5])); 113 builder.end(end); 114 } 115 116 return builder.build(); 117 } 118 119 public ACE() { 120 this(null, null, false); 121 } 122 123 /** 124 * Constructs an ACE for a given username and permission, and specifies whether to grant or deny it. 125 */ 126 public ACE(String username, String permission, boolean isGranted) { 127 this(username, permission, isGranted, null, null, null, null); 128 } 129 130 /** 131 * Constructs an ACE for a given username and permission. 132 * <p> 133 * The ACE is granted. 134 * 135 * @since 6.0 136 */ 137 public ACE(String username, String permission) { 138 this(username, permission, true); 139 } 140 141 /** 142 * Constructs an ACE for a given username, permission, specifying whether to grant or deny it, creator user, begin 143 * and end date. 144 * 145 * @since 7.4 146 */ 147 ACE(String username, String permission, boolean isGranted, String creator, Calendar begin, Calendar end, 148 Map<String, Serializable> contextData) { 149 this.username = username; 150 this.permission = permission; 151 this.isGranted = isGranted; 152 this.creator = creator; 153 setBegin(begin); 154 setEnd(end); 155 if (contextData != null) { 156 this.contextData = new HashMap<>(contextData); 157 } 158 159 if (begin != null && end != null) { 160 if (begin.after(end)) { 161 throw new IllegalArgumentException("'begin' date cannot be after 'end' date"); 162 } 163 } 164 } 165 166 /** 167 * Returns this ACE id. 168 * <p> 169 * This id is unique inside a given ACL. 170 * 171 * @since 7.4 172 */ 173 public String getId() { 174 StringBuilder sb = new StringBuilder(); 175 sb.append(username); 176 sb.append(':'); 177 sb.append(permission); 178 sb.append(':'); 179 sb.append(isGranted); 180 181 sb.append(':'); 182 if (creator != null) { 183 sb.append(creator); 184 } 185 186 sb.append(':'); 187 if (begin != null) { 188 sb.append(begin.getTimeInMillis()); 189 } 190 191 sb.append(':'); 192 if (end != null) { 193 sb.append(end.getTimeInMillis()); 194 } 195 196 return sb.toString(); 197 } 198 199 public String getUsername() { 200 return username; 201 } 202 203 public String getPermission() { 204 return permission; 205 } 206 207 /** 208 * Checks if this privilege is granted. 209 * 210 * @return true if the privilege is granted 211 */ 212 public boolean isGranted() { 213 return isGranted; 214 } 215 216 /** 217 * Checks if this privilege is denied. 218 * 219 * @return true if privilege is denied 220 */ 221 public boolean isDenied() { 222 return !isGranted; 223 } 224 225 public Calendar getBegin() { 226 return begin; 227 } 228 229 /** 230 * Sets the begin date of this ACE. 231 * <p> 232 * Sets the {@code Calendar.MILLISECOND} part of the Calendar to 0. 233 */ 234 public void setBegin(Calendar begin) { 235 this.begin = begin; 236 if (this.begin != null) { 237 this.begin.set(Calendar.MILLISECOND, 0); 238 } 239 } 240 241 public Calendar getEnd() { 242 return end; 243 } 244 245 /** 246 * Sets the end date of this ACE. 247 * <p> 248 * Sets the {@code Calendar.MILLISECOND} part of the Calendar to 0. 249 */ 250 public void setEnd(Calendar end) { 251 this.end = end; 252 if (this.end != null) { 253 this.end.set(Calendar.MILLISECOND, 0); 254 } 255 } 256 257 public String getCreator() { 258 return creator; 259 } 260 261 public void setCreator(String creator) { 262 this.creator = creator; 263 } 264 265 /** 266 * Returns the status of this ACE. 267 * 268 * @since 7.4 269 */ 270 public Status getStatus() { 271 Status status = Status.EFFECTIVE; 272 Calendar now = new GregorianCalendar(); 273 if (begin != null && now.before(begin)) { 274 status = Status.PENDING; 275 } 276 if (end != null && now.after(end)) { 277 status = Status.ARCHIVED; 278 } 279 return status; 280 } 281 282 /** 283 * Returns a Long value of this ACE status. 284 * <p> 285 * It returns {@code null} if there is no begin and end date, which means the ACE is effective. Otherwise, it 286 * returns 0 for PENDING, 1 for EFFECTIVE and 2 for ARCHIVED. 287 * 288 * @since 7.4 289 */ 290 public Long getLongStatus() { 291 if (begin == null && end == null) { 292 return null; 293 } 294 return Long.valueOf(getStatus().ordinal()); 295 } 296 297 public boolean isEffective() { 298 return getStatus() == Status.EFFECTIVE; 299 } 300 301 public boolean isPending() { 302 return getStatus() == Status.PENDING; 303 } 304 305 public boolean isArchived() { 306 return getStatus() == Status.ARCHIVED; 307 } 308 309 public Serializable getContextData(String key) { 310 return contextData.get(key); 311 } 312 313 public void putContextData(String key, Serializable value) { 314 contextData.put(key, value); 315 } 316 317 @Override 318 public boolean equals(Object obj) { 319 if (this == obj) { 320 return true; 321 } 322 if (obj instanceof ACE) { 323 ACE ace = (ACE) obj; 324 // check Calendars without handling timezone 325 boolean beginEqual = ace.begin == null && begin == null 326 || !(ace.begin == null || begin == null) && ace.begin.getTimeInMillis() == begin.getTimeInMillis(); 327 boolean endEqual = ace.end == null && end == null 328 || !(ace.end == null || end == null) && ace.end.getTimeInMillis() == end.getTimeInMillis(); 329 boolean creatorEqual = Objects.equals(ace.creator, creator); 330 boolean usernameEqual = Objects.equals(ace.username, username); 331 boolean permissionEqual = Objects.equals(ace.permission, permission); 332 return ace.isGranted == isGranted && usernameEqual && permissionEqual && creatorEqual && beginEqual 333 && endEqual; 334 } 335 return super.equals(obj); 336 } 337 338 @Override 339 public int hashCode() { 340 int hash = 17; 341 hash = hash * 37 + (isGranted ? 1 : 0); 342 hash = username != null ? hash * 37 + username.hashCode() : hash; 343 hash = creator != null ? hash * 37 + creator.hashCode() : hash; 344 hash = begin != null ? hash * 37 + begin.hashCode() : hash; 345 hash = end != null ? hash * 37 + end.hashCode() : hash; 346 hash = permission != null ? hash * 37 + permission.hashCode() : hash; 347 return hash; 348 } 349 350 @Override 351 public String toString() { 352 StringBuilder sb = new StringBuilder(); 353 sb.append(getClass().getSimpleName()); 354 sb.append('('); 355 sb.append("username=").append(username); 356 sb.append(", "); 357 sb.append("permission=").append(permission); 358 sb.append(", "); 359 sb.append("isGranted=").append(isGranted); 360 sb.append(", "); 361 sb.append("creator=").append(creator); 362 sb.append(", "); 363 sb.append("begin=").append(begin != null ? begin.getTimeInMillis() : null); 364 sb.append(", "); 365 sb.append("end=").append(end != null ? end.getTimeInMillis() : null); 366 sb.append(')'); 367 return sb.toString(); 368 } 369 370 @Override 371 public Object clone() { 372 return new ACE(username, permission, isGranted, creator, begin, end, contextData); 373 } 374 375 /** @since 11.3 */ 376 public static ACEBuilder builder(ACE ace) { 377 return new ACEBuilder(ace); 378 } 379 380 public static ACEBuilder builder(String username, String permission) { 381 return new ACEBuilder(username, permission); 382 } 383 384 public static class ACEBuilder { 385 386 private String username; 387 388 private String permission; 389 390 private boolean isGranted = true; 391 392 private Calendar begin; 393 394 private Calendar end; 395 396 private String creator; 397 398 private Map<String, Serializable> contextData; 399 400 /** @since 11.3 */ 401 public ACEBuilder(ACE ace) { 402 this.username = ace.username; 403 this.permission = ace.permission; 404 this.isGranted = ace.isGranted; 405 this.begin = ace.begin; 406 this.end = ace.end; 407 this.creator = ace.creator; 408 this.contextData = ace.contextData; 409 } 410 411 public ACEBuilder(String username, String permission) { 412 this.username = username; 413 this.permission = permission; 414 } 415 416 /** @since 11.3 */ 417 public ACEBuilder username(String username) { 418 this.username = username; 419 return this; 420 } 421 422 /** @since 11.3 */ 423 public ACEBuilder permission(String permission) { 424 this.permission = permission; 425 return this; 426 } 427 428 public ACEBuilder isGranted(boolean isGranted) { 429 this.isGranted = isGranted; 430 return this; 431 } 432 433 public ACEBuilder begin(Calendar begin) { 434 this.begin = begin; 435 return this; 436 } 437 438 public ACEBuilder end(Calendar end) { 439 this.end = end; 440 return this; 441 } 442 443 public ACEBuilder creator(String creator) { 444 this.creator = creator; 445 return this; 446 } 447 448 public ACEBuilder contextData(Map<String, Serializable> contextData) { 449 this.contextData = contextData; 450 return this; 451 } 452 453 public ACE build() { 454 return new ACE(username, permission, isGranted, creator, begin, end, contextData); 455 } 456 } 457 458}