6e1057b806dc83b7a956e26aaca0681952ae5a31
[aaf/authz.git] / auth / auth-cass / src / main / java / org / onap / aaf / auth / dao / cass / PermDAO.java
1 /**
2  * ============LICENSE_START====================================================
3  * org.onap.aaf
4  * ===========================================================================
5  * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
6  * ===========================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END====================================================
19  *
20  */
21
22 package org.onap.aaf.auth.dao.cass;
23
24 import java.io.ByteArrayOutputStream;
25 import java.io.DataInputStream;
26 import java.io.DataOutputStream;
27 import java.io.IOException;
28 import java.nio.ByteBuffer;
29 import java.util.HashSet;
30 import java.util.List;
31 import java.util.Set;
32
33 import org.onap.aaf.auth.dao.Bytification;
34 import org.onap.aaf.auth.dao.Cached;
35 import org.onap.aaf.auth.dao.CassAccess;
36 import org.onap.aaf.auth.dao.CassDAOImpl;
37 import org.onap.aaf.auth.dao.DAOException;
38 import org.onap.aaf.auth.dao.Loader;
39 import org.onap.aaf.auth.dao.Streamer;
40 import org.onap.aaf.auth.dao.hl.Question;
41 import org.onap.aaf.auth.env.AuthzTrans;
42 import org.onap.aaf.auth.layer.Result;
43 import org.onap.aaf.misc.env.APIException;
44 import org.onap.aaf.misc.env.util.Split;
45
46 import com.datastax.driver.core.Cluster;
47 import com.datastax.driver.core.Row;
48 import com.datastax.driver.core.exceptions.DriverException;
49
50 public class PermDAO extends CassDAOImpl<AuthzTrans,PermDAO.Data> {
51
52     public static final String TABLE = "perm";
53
54     public static final int CACHE_SEG = 0x40; // yields segment 0x0-0x3F
55     private static final String STAR = "*";
56     
57     private final HistoryDAO historyDAO;
58     private final CacheInfoDAO infoDAO;
59     
60     private PSInfo psNS, psChildren, psByType;
61
62     public PermDAO(AuthzTrans trans, Cluster cluster, String keyspace) throws APIException, IOException {
63         super(trans, PermDAO.class.getSimpleName(),cluster,keyspace,Data.class,TABLE, readConsistency(trans,TABLE), writeConsistency(trans,TABLE));
64         init(trans);
65         historyDAO = new HistoryDAO(trans, this);
66         infoDAO = new CacheInfoDAO(trans,this);
67     }
68
69     public PermDAO(AuthzTrans trans, HistoryDAO hDAO, CacheInfoDAO ciDAO) {
70         super(trans, PermDAO.class.getSimpleName(),hDAO,Data.class,TABLE, readConsistency(trans,TABLE), writeConsistency(trans,TABLE));
71         historyDAO = hDAO;
72         infoDAO=ciDAO;
73         init(trans);
74     }
75
76
77     private static final int KEYLIMIT = 4;
78     public static class Data extends CacheableData implements Bytification {
79         public String        ns;
80         public String        type;
81         public String        instance;
82         public String        action;
83         public Set<String>  roles; 
84         public String        description;
85
86         public Data() {}
87         
88         public Data(NsSplit nss, String instance, String action) {
89             ns = nss.ns;
90             type = nss.name;
91             this.instance = instance;
92             this.action = action;
93         }
94
95         public String fullType() {
96             return ns + '.' + type;
97         }
98         
99         public String fullPerm() {
100             return ns + '.' + type + '|' + instance + '|' + action;
101         }
102
103         public String encode() {
104             return ns + '|' + type + '|' + instance + '|' + action;
105         }
106         
107         /**
108          * Decode Perm String, including breaking into appropriate Namespace
109          * 
110          * @param trans
111          * @param q
112          * @param p
113          * @return
114          */
115         public static Result<Data> decode(AuthzTrans trans, Question q, String p) {
116             String[] ss = Split.splitTrim('|', p,4);
117             if (ss[2]==null) {
118                 return Result.err(Status.ERR_BadData,"Perm Encodings must be separated by '|'");
119             }
120             Data data = new Data();
121             if (ss[3]==null) { // older 3 part encoding must be evaluated for NS
122                 Result<NsSplit> nss = q.deriveNsSplit(trans, ss[0]);
123                 if (nss.notOK()) {
124                     return Result.err(nss);
125                 }
126                 data.ns=nss.value.ns;
127                 data.type=nss.value.name;
128                 data.instance=ss[1];
129                 data.action=ss[2];
130             } else { // new 4 part encoding
131                 data.ns=ss[0];
132                 data.type=ss[1];
133                 data.instance=ss[2];
134                 data.action=ss[3];
135             }
136             return Result.ok(data);
137         }
138
139         /**
140          * Decode Perm String, including breaking into appropriate Namespace
141          * 
142          * @param trans
143          * @param q
144          * @param p
145          * @return
146          */
147         public static Result<String[]> decodeToArray(AuthzTrans trans, Question q, String p) {
148             String[] ss = Split.splitTrim('|', p,4);
149             if (ss[2]==null) {
150                 return Result.err(Status.ERR_BadData,"Perm Encodings must be separated by '|'");
151             }
152             
153             if (ss[3]==null) { // older 3 part encoding must be evaluated for NS
154                 ss[3] = ss[2];
155                 ss[2] = ss[1];
156                 Result<NsSplit> nss = q.deriveNsSplit(trans, ss[0]);
157                 if (nss.notOK()) {
158                     return Result.err(nss);
159                 }
160                 ss[1] = nss.value.name;
161                 ss[0] = nss.value.ns;
162             }
163             return Result.ok(ss);
164         }
165
166         public static Data create(NsDAO.Data ns, String name) {
167             NsSplit nss = new NsSplit(ns,name);
168             Data rv = new Data();
169             rv.ns = nss.ns;
170             String[] s = nss.name.split("\\|");
171             switch(s.length) {
172                 case 3:
173                     rv.type=s[0];
174                     rv.instance=s[1];
175                     rv.action=s[2];
176                     break;
177                 case 2:
178                     rv.type=s[0];
179                     rv.instance=s[1];
180                     rv.action=STAR;
181                     break;
182                 default:
183                     rv.type=s[0];
184                     rv.instance = STAR;
185                     rv.action = STAR;
186             }
187             return rv;
188         }
189         
190         public static Data create(AuthzTrans trans, Question q, String name) {
191             String[] s = name.split("\\|");
192             Result<NsSplit> rdns = q.deriveNsSplit(trans, s[0]);
193             Data rv = new PermDAO.Data();
194             if (rdns.isOKhasData()) {
195                 switch(s.length) {
196                     case 3:
197                         rv.type=s[1];
198                         rv.instance=s[2];
199                         rv.action=s[3];
200                         break;
201                     case 2:
202                         rv.type=s[1];
203                         rv.instance=s[2];
204                         rv.action=STAR;
205                         break;
206                     default:
207                         rv.type=s[1];
208                         rv.instance = STAR;
209                         rv.action = STAR;
210                 }
211             }
212             return rv;
213         }
214         
215         ////////////////////////////////////////
216         // Getters
217         public Set<String> roles(boolean mutable) {
218             if (roles == null) {
219                 roles = new HashSet<>();
220             } else if (mutable && !(roles instanceof HashSet)) {
221                 roles = new HashSet<>(roles);
222             }
223             return roles;
224         }
225
226         @Override
227         public int[] invalidate(Cached<?,?> cache) {
228             return new int[] {
229                 seg(cache,ns),
230                 seg(cache,ns,type),
231                 seg(cache,ns,type,STAR),
232                 seg(cache,ns,type,instance,action)
233             };
234         }
235
236         @Override
237         public ByteBuffer bytify() throws IOException {
238             ByteArrayOutputStream baos = new ByteArrayOutputStream();
239             PermLoader.deflt.marshal(this, new DataOutputStream(baos));
240             return ByteBuffer.wrap(baos.toByteArray());
241         }
242         
243         @Override
244         public void reconstitute(ByteBuffer bb) throws IOException {
245             PermLoader.deflt.unmarshal(this, toDIS(bb));
246         }
247
248         @Override
249         public String toString() {
250             return encode();
251         }
252     }
253     
254     private static class PermLoader extends Loader<Data> implements Streamer<Data> {
255         public static final int MAGIC=283939453;
256         public static final int VERSION=1;
257         public static final int BUFF_SIZE=96;
258
259         public static final PermLoader deflt = new PermLoader(KEYLIMIT);
260         
261         public PermLoader(int keylimit) {
262             super(keylimit);
263         }
264         
265         @Override
266         public Data load(Data data, Row row) {
267             // Int more efficient Match "fields" string
268             data.ns = row.getString(0);
269             data.type = row.getString(1);
270             data.instance = row.getString(2);
271             data.action = row.getString(3);
272             data.roles = row.getSet(4,String.class);
273             data.description = row.getString(5);
274             return data;
275         }
276
277         @Override
278         protected void key(Data data, int _idx, Object[] obj) {
279                 int idx = _idx;
280             obj[idx]=data.ns;
281             obj[++idx]=data.type;
282             obj[++idx]=data.instance;
283             obj[++idx]=data.action;
284         }
285
286         @Override
287         protected void body(Data data, int _idx, Object[] obj) {
288                 int idx = _idx;
289             obj[idx]=data.roles;
290             obj[++idx]=data.description;
291         }
292
293         @Override
294         public void marshal(Data data, DataOutputStream os) throws IOException {
295             writeHeader(os,MAGIC,VERSION);
296             writeString(os, data.ns);
297             writeString(os, data.type);
298             writeString(os, data.instance);
299             writeString(os, data.action);
300             writeStringSet(os, data.roles);
301             writeString(os, data.description);
302         }
303
304         @Override
305         public void unmarshal(Data data, DataInputStream is) throws IOException {
306             /*int version = */readHeader(is,MAGIC,VERSION);
307             // If Version Changes between Production runs, you'll need to do a switch Statement, and adequately read in fields
308             byte[] buff = new byte[BUFF_SIZE];
309             data.ns = readString(is, buff);
310             data.type = readString(is,buff);
311             data.instance = readString(is,buff);
312             data.action = readString(is,buff);
313             data.roles = readStringSet(is,buff);
314             data.description = readString(is,buff);
315         }
316     }
317     
318     private void init(AuthzTrans trans) {
319         // the 3 is the number of key fields
320         String[] helpers = setCRUD(trans, TABLE, Data.class, PermLoader.deflt);
321         
322         // Other SELECT style statements... match with a local Method
323         psByType = new PSInfo(trans, SELECT_SP + helpers[FIELD_COMMAS] + " FROM " + TABLE + 
324                 " WHERE ns = ? AND type = ?", new PermLoader(2) {
325             @Override
326             protected void key(Data data, int idx, Object[] obj) {
327                 obj[idx]=data.type;
328             }
329         },readConsistency);
330         
331         psNS = new PSInfo(trans, SELECT_SP + helpers[FIELD_COMMAS] + " FROM " + TABLE +
332                 " WHERE ns = ?", new PermLoader(1),readConsistency);
333                 
334         psChildren = new PSInfo(trans, SELECT_SP +  helpers[FIELD_COMMAS] +  " FROM " + TABLE + 
335                 " WHERE ns=? AND type > ? AND type < ?", 
336                 new PermLoader(3) {
337             @Override
338             protected void key(Data data, int _idx, Object[] obj) {
339                     int idx = _idx;
340                 obj[idx] = data.ns;
341                 obj[++idx]=data.type + DOT;
342                 obj[++idx]=data.type + DOT_PLUS_ONE;
343             }
344         },readConsistency);
345
346     }
347
348
349     /**
350      * Add a single Permission to the Role's Permission Collection
351      * 
352      * @param trans
353      * @param roleFullName
354      * @param perm
355      * @param type
356      * @param action
357      * @return
358      */
359     public Result<Void> addRole(AuthzTrans trans, PermDAO.Data perm, String roleFullName) {
360         // Note: Prepared Statements for Collection updates aren't supported
361         //ResultSet rv =
362         try {
363             getSession(trans).execute(UPDATE_SP + TABLE + " SET roles = roles + {'"    + roleFullName + "'} " +
364                 "WHERE " +
365                     "ns = '" + perm.ns + "' AND " +
366                     "type = '" + perm.type + "' AND " +
367                     "instance = '" + perm.instance + "' AND " +
368                     "action = '" + perm.action + "';"
369                     );
370         } catch (DriverException | APIException | IOException e) {
371             reportPerhapsReset(trans,e);
372             return Result.err(Result.ERR_Backend, CassAccess.ERR_ACCESS_MSG);
373         }
374
375         wasModified(trans, CRUD.update, perm, "Added role " + roleFullName + " to perm " +
376                 perm.ns + '.' + perm.type + '|' + perm.instance + '|' + perm.action);
377         return Result.ok();
378     }
379
380     /**
381      * Remove a single Permission from the Role's Permission Collection
382      * @param trans
383      * @param roleFullName
384      * @param perm
385      * @param type
386      * @param action
387      * @return
388      */
389     public Result<Void> delRole(AuthzTrans trans, PermDAO.Data perm, String roleFullName) {
390         // Note: Prepared Statements for Collection updates aren't supported
391         //ResultSet rv =
392         try {
393             getSession(trans).execute(UPDATE_SP + TABLE + " SET roles = roles - {'" + roleFullName + "'} " +
394                 "WHERE " +
395                     "ns = '" + perm.ns + "' AND " +
396                     "type = '" + perm.type + "' AND " +
397                     "instance = '" + perm.instance + "' AND " +
398                     "action = '" + perm.action + "';"
399                     );
400         } catch (DriverException | APIException | IOException e) {
401             reportPerhapsReset(trans,e);
402             return Result.err(Result.ERR_Backend, CassAccess.ERR_ACCESS_MSG);
403         }
404
405         //TODO how can we tell when it doesn't?
406         wasModified(trans, CRUD.update, perm, "Removed role " + roleFullName + " from perm " +
407                 perm.ns + '.' + perm.type + '|' + perm.instance + '|' + perm.action);
408         return Result.ok();
409     }
410
411
412     
413     /**
414      * Additional method: 
415      *         Select all Permissions by Name
416      * 
417      * @param name
418      * @return
419      * @throws DAOException
420      */
421     public Result<List<Data>> readByType(AuthzTrans trans, String ns, String type) {
422         return psByType.read(trans, R_TEXT, new Object[]{ns, type});
423     }
424     
425     public Result<List<Data>> readChildren(AuthzTrans trans, String ns, String type) {
426         return psChildren.read(trans, R_TEXT, new Object[]{ns, type+DOT, type + DOT_PLUS_ONE});
427     }
428
429     public Result<List<Data>> readNS(AuthzTrans trans, String ns) {
430         return psNS.read(trans, R_TEXT, new Object[]{ns});
431     }
432
433     /**
434      * Add description to this permission
435      * 
436      * @param trans
437      * @param ns
438      * @param type
439      * @param instance
440      * @param action
441      * @param description
442      * @return
443      */
444     public Result<Void> addDescription(AuthzTrans trans, String ns, String type,
445             String instance, String action, String description) {
446         try {
447             getSession(trans).execute(UPDATE_SP + TABLE + " SET description = '" 
448                 + description + "' WHERE ns = '" + ns + "' AND type = '" + type + "'"
449                 + "AND instance = '" + instance + "' AND action = '" + action + "';");
450         } catch (DriverException | APIException | IOException e) {
451             reportPerhapsReset(trans,e);
452             return Result.err(Result.ERR_Backend, CassAccess.ERR_ACCESS_MSG);
453         }
454
455         Data data = new Data();
456         data.ns=ns;
457         data.type=type;
458         data.instance=instance;
459         data.action=action;
460         wasModified(trans, CRUD.update, data, "Added description " + description + " to permission " 
461                 + data.encode(), null );
462         return Result.ok();
463     }
464     
465     /**
466      * Log Modification statements to History
467      */
468     @Override
469     protected void wasModified(AuthzTrans trans, CRUD modified, Data data, String ... override) {
470         boolean memo = override.length>0 && override[0]!=null;
471         boolean subject = override.length>1 && override[1]!=null;
472
473         // Need to update history
474         HistoryDAO.Data hd = HistoryDAO.newInitedData();
475         hd.user = trans.user();
476         hd.action = modified.name();
477         hd.target = TABLE;
478         hd.subject = subject ? override[1] : data.fullType();
479         if (memo) {
480             hd.memo = String.format("%s", override[0]);
481         } else {
482             hd.memo = String.format("%sd %s|%s|%s", modified.name(),data.fullType(),data.instance,data.action);
483         }
484         
485         if (modified==CRUD.delete) {
486             try {
487                 hd.reconstruct = data.bytify();
488             } catch (IOException e) {
489                 trans.error().log(e,"Could not serialize PermDAO.Data");
490             }
491         }
492         
493         if (historyDAO.create(trans, hd).status!=Status.OK) {
494             trans.error().log("Cannot log to History");
495         }
496         if (infoDAO.touch(trans, TABLE,data.invalidate(cache)).notOK()) {
497             trans.error().log("Cannot touch CacheInfo");
498         }
499     }
500 }
501