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}