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