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