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