Update for more Logging Info
[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                 StringBuilder sb = new StringBuilder();
97                 if(ns==null) {
98                         sb.append('.');
99                 } else {
100                         sb.append(ns);
101                         sb.append(ns.indexOf('@')<0?'.':':');
102                 }
103                 sb.append(type);
104                 return sb.toString();
105         }
106         
107         public String fullPerm() {
108                 StringBuilder sb = new StringBuilder(ns);
109                 sb.append(ns.indexOf('@')<0?'.':':'); 
110                 sb.append(type);
111                 sb.append('|');
112                 sb.append(instance);
113                 sb.append('|');
114                 sb.append(action);
115                 return sb.toString();
116         }
117
118         public String encode() {
119             return ns + '|' + type + '|' + instance + '|' + action;
120         }
121         
122         /**
123          * Decode Perm String, including breaking into appropriate Namespace
124          * 
125          * @param trans
126          * @param q
127          * @param p
128          * @return
129          */
130         public static Result<Data> decode(AuthzTrans trans, Question q, String p) {
131             String[] ss = Split.splitTrim('|', p,4);
132             if (ss[2]==null) {
133                 return Result.err(Status.ERR_BadData,"Perm Encodings must be separated by '|'");
134             }
135             Data data = new Data();
136             if (ss[3]==null) { // older 3 part encoding must be evaluated for NS
137                 Result<NsSplit> nss = q.deriveNsSplit(trans, ss[0]);
138                 if (nss.notOK()) {
139                     return Result.err(nss);
140                 }
141                 data.ns=nss.value.ns;
142                 data.type=nss.value.name;
143                 data.instance=ss[1];
144                 data.action=ss[2];
145             } else { // new 4 part encoding
146                 data.ns=ss[0];
147                 data.type=ss[1];
148                 data.instance=ss[2];
149                 data.action=ss[3];
150             }
151             return Result.ok(data);
152         }
153
154         /**
155          * Decode Perm String, including breaking into appropriate Namespace
156          * 
157          * @param trans
158          * @param q
159          * @param p
160          * @return
161          */
162         public static Result<String[]> decodeToArray(AuthzTrans trans, Question q, String p) {
163             String[] ss = Split.splitTrim('|', p,4);
164             if (ss[2]==null) {
165                 return Result.err(Status.ERR_BadData,"Perm Encodings must be separated by '|'");
166             }
167             
168             if (ss[3]==null) { // older 3 part encoding must be evaluated for NS
169                 ss[3] = ss[2];
170                 ss[2] = ss[1];
171                 Result<NsSplit> nss = q.deriveNsSplit(trans, ss[0]);
172                 if (nss.notOK()) {
173                     return Result.err(nss);
174                 }
175                 ss[1] = nss.value.name;
176                 ss[0] = nss.value.ns;
177             }
178             return Result.ok(ss);
179         }
180
181         public static Data create(NsDAO.Data ns, String name) {
182             NsSplit nss = new NsSplit(ns,name);
183             Data rv = new Data();
184             rv.ns = nss.ns;
185             String[] s = nss.name.split("\\|");
186             switch(s.length) {
187                 case 3:
188                     rv.type=s[0];
189                     rv.instance=s[1];
190                     rv.action=s[2];
191                     break;
192                 case 2:
193                     rv.type=s[0];
194                     rv.instance=s[1];
195                     rv.action=STAR;
196                     break;
197                 default:
198                     rv.type=s[0];
199                     rv.instance = STAR;
200                     rv.action = STAR;
201             }
202             return rv;
203         }
204         
205         public static Data create(AuthzTrans trans, Question q, String name) {
206             String[] s = name.split("\\|");
207             Result<NsSplit> rdns = q.deriveNsSplit(trans, s[0]);
208             Data rv = new PermDAO.Data();
209             if (rdns.isOKhasData()) {
210                 switch(s.length) {
211                         case 4:
212                                 rv.ns=s[0];
213                             rv.type=s[1];
214                             rv.instance=s[2];
215                             rv.action=s[3];
216                             break;
217                     case 3:
218                                 rv.ns=s[0];
219                         rv.type=s[1];
220                         rv.instance=s[2];
221                         rv.action=s[3];
222                         break;
223                     case 2:
224                                 rv.ns=s[0];
225                         rv.type=s[1];
226                         rv.instance=s[2];
227                         rv.action=STAR;
228                         break;
229                     default:
230                                 rv.ns=s[0];
231                         rv.type=s[1];
232                         rv.instance = STAR;
233                         rv.action = STAR;
234                 }
235             }
236             return rv;
237         }
238         
239         ////////////////////////////////////////
240         // Getters
241         public Set<String> roles(boolean mutable) {
242             if (roles == null) {
243                 roles = new HashSet<>();
244             } else if (mutable && !(roles instanceof HashSet)) {
245                 roles = new HashSet<>(roles);
246             }
247             return roles;
248         }
249
250         @Override
251         public int[] invalidate(Cached<?,?> cache) {
252             return new int[] {
253                 seg(cache,ns),
254                 seg(cache,ns,type),
255                 seg(cache,ns,type,STAR),
256                 seg(cache,ns,type,instance,action)
257             };
258         }
259
260         @Override
261         public ByteBuffer bytify() throws IOException {
262             ByteArrayOutputStream baos = new ByteArrayOutputStream();
263             PermLoader.deflt.marshal(this, new DataOutputStream(baos));
264             return ByteBuffer.wrap(baos.toByteArray());
265         }
266         
267         @Override
268         public void reconstitute(ByteBuffer bb) throws IOException {
269             PermLoader.deflt.unmarshal(this, toDIS(bb));
270         }
271
272         @Override
273         public String toString() {
274             return encode();
275         }
276     }
277     
278     private static class PermLoader extends Loader<Data> implements Streamer<Data> {
279         public static final int MAGIC=283939453;
280         public static final int VERSION=1;
281         public static final int BUFF_SIZE=96;
282
283         public static final PermLoader deflt = new PermLoader(KEYLIMIT);
284         
285         public PermLoader(int keylimit) {
286             super(keylimit);
287         }
288         
289         @Override
290         public Data load(Data data, Row row) {
291             // Int more efficient Match "fields" string
292             data.ns = row.getString(0);
293             data.type = row.getString(1);
294             data.instance = row.getString(2);
295             data.action = row.getString(3);
296             data.roles = row.getSet(4,String.class);
297             data.description = row.getString(5);
298             return data;
299         }
300
301         @Override
302         protected void key(Data data, int _idx, Object[] obj) {
303                 int idx = _idx;
304             obj[idx]=data.ns;
305             obj[++idx]=data.type;
306             obj[++idx]=data.instance;
307             obj[++idx]=data.action;
308         }
309
310         @Override
311         protected void body(Data data, int _idx, Object[] obj) {
312                 int idx = _idx;
313             obj[idx]=data.roles;
314             obj[++idx]=data.description;
315         }
316
317         @Override
318         public void marshal(Data data, DataOutputStream os) throws IOException {
319             writeHeader(os,MAGIC,VERSION);
320             writeString(os, data.ns);
321             writeString(os, data.type);
322             writeString(os, data.instance);
323             writeString(os, data.action);
324             writeStringSet(os, data.roles);
325             writeString(os, data.description);
326         }
327
328         @Override
329         public void unmarshal(Data data, DataInputStream is) throws IOException {
330             /*int version = */readHeader(is,MAGIC,VERSION);
331             // If Version Changes between Production runs, you'll need to do a switch Statement, and adequately read in fields
332             byte[] buff = new byte[BUFF_SIZE];
333             data.ns = readString(is, buff);
334             data.type = readString(is,buff);
335             data.instance = readString(is,buff);
336             data.action = readString(is,buff);
337             data.roles = readStringSet(is,buff);
338             data.description = readString(is,buff);
339         }
340     }
341     
342     private void init(AuthzTrans trans) {
343         // the 3 is the number of key fields
344         String[] helpers = setCRUD(trans, TABLE, Data.class, PermLoader.deflt);
345         
346         // Other SELECT style statements... match with a local Method
347         psByType = new PSInfo(trans, SELECT_SP + helpers[FIELD_COMMAS] + " FROM " + TABLE + 
348                 " WHERE ns = ? AND type = ?", new PermLoader(2) {
349             @Override
350             protected void key(Data data, int idx, Object[] obj) {
351                 obj[idx]=data.type;
352             }
353         },readConsistency);
354         
355         psNS = new PSInfo(trans, SELECT_SP + helpers[FIELD_COMMAS] + " FROM " + TABLE +
356                 " WHERE ns = ?", new PermLoader(1),readConsistency);
357                 
358         psChildren = new PSInfo(trans, SELECT_SP +  helpers[FIELD_COMMAS] +  " FROM " + TABLE + 
359                 " WHERE ns=? AND type > ? AND type < ?", 
360                 new PermLoader(3) {
361             @Override
362             protected void key(Data data, int _idx, Object[] obj) {
363                     int idx = _idx;
364                 obj[idx] = data.ns;
365                 obj[++idx]=data.type + DOT;
366                 obj[++idx]=data.type + DOT_PLUS_ONE;
367             }
368         },readConsistency);
369
370     }
371
372
373     /**
374      * Add a single Permission to the Role's Permission Collection
375      * 
376      * @param trans
377      * @param roleFullName
378      * @param perm
379      * @param type
380      * @param action
381      * @return
382      */
383     public Result<Void> addRole(AuthzTrans trans, PermDAO.Data perm, String roleFullName) {
384         // Note: Prepared Statements for Collection updates aren't supported
385         //ResultSet rv =
386         try {
387             getSession(trans).execute(UPDATE_SP + TABLE + " SET roles = roles + {'"    + roleFullName + "'} " +
388                 "WHERE " +
389                     "ns = '" + perm.ns + "' AND " +
390                     "type = '" + perm.type + "' AND " +
391                     "instance = '" + perm.instance + "' AND " +
392                     "action = '" + perm.action + "';"
393                     );
394         } catch (DriverException | APIException | IOException e) {
395             reportPerhapsReset(trans,e);
396             return Result.err(Result.ERR_Backend, CassAccess.ERR_ACCESS_MSG);
397         }
398
399         wasModified(trans, CRUD.update, perm, "Added role " + roleFullName + " to perm " +
400                 perm.ns + '.' + perm.type + '|' + perm.instance + '|' + perm.action);
401         return Result.ok();
402     }
403
404     /**
405      * Remove a single Permission from the Role's Permission Collection
406      * @param trans
407      * @param roleFullName
408      * @param perm
409      * @param type
410      * @param action
411      * @return
412      */
413     public Result<Void> delRole(AuthzTrans trans, PermDAO.Data perm, String roleFullName) {
414         // Note: Prepared Statements for Collection updates aren't supported
415         //ResultSet rv =
416         try {
417             getSession(trans).execute(UPDATE_SP + TABLE + " SET roles = roles - {'" + roleFullName + "'} " +
418                 "WHERE " +
419                     "ns = '" + perm.ns + "' AND " +
420                     "type = '" + perm.type + "' AND " +
421                     "instance = '" + perm.instance + "' AND " +
422                     "action = '" + perm.action + "';"
423                     );
424         } catch (DriverException | APIException | IOException e) {
425             reportPerhapsReset(trans,e);
426             return Result.err(Result.ERR_Backend, CassAccess.ERR_ACCESS_MSG);
427         }
428
429         //TODO how can we tell when it doesn't?
430         wasModified(trans, CRUD.update, perm, "Removed role " + roleFullName + " from perm " +
431                 perm.ns + '.' + perm.type + '|' + perm.instance + '|' + perm.action);
432         return Result.ok();
433     }
434
435
436     
437     /**
438      * Additional method: 
439      *         Select all Permissions by Name
440      * 
441      * @param name
442      * @return
443      * @throws DAOException
444      */
445     public Result<List<Data>> readByType(AuthzTrans trans, String ns, String type) {
446         return psByType.read(trans, R_TEXT, new Object[]{ns, type});
447     }
448     
449     public Result<List<Data>> readChildren(AuthzTrans trans, String ns, String type) {
450         return psChildren.read(trans, R_TEXT, new Object[]{ns, type+DOT, type + DOT_PLUS_ONE});
451     }
452
453     public Result<List<Data>> readNS(AuthzTrans trans, String ns) {
454         return psNS.read(trans, R_TEXT, new Object[]{ns});
455     }
456
457     /**
458      * Add description to this permission
459      * 
460      * @param trans
461      * @param ns
462      * @param type
463      * @param instance
464      * @param action
465      * @param description
466      * @return
467      */
468     public Result<Void> addDescription(AuthzTrans trans, String ns, String type,
469             String instance, String action, String description) {
470         try {
471             getSession(trans).execute(UPDATE_SP + TABLE + " SET description = '" 
472                 + description + "' WHERE ns = '" + ns + "' AND type = '" + type + "'"
473                 + "AND instance = '" + instance + "' AND action = '" + action + "';");
474         } catch (DriverException | APIException | IOException e) {
475             reportPerhapsReset(trans,e);
476             return Result.err(Result.ERR_Backend, CassAccess.ERR_ACCESS_MSG);
477         }
478
479         Data data = new Data();
480         data.ns=ns;
481         data.type=type;
482         data.instance=instance;
483         data.action=action;
484         wasModified(trans, CRUD.update, data, "Added description " + description + " to permission " 
485                 + data.encode(), null );
486         return Result.ok();
487     }
488     
489     /**
490      * Log Modification statements to History
491      */
492     @Override
493     protected void wasModified(AuthzTrans trans, CRUD modified, Data data, String ... override) {
494         boolean memo = override.length>0 && override[0]!=null;
495         boolean subject = override.length>1 && override[1]!=null;
496
497         // Need to update history
498         HistoryDAO.Data hd = HistoryDAO.newInitedData();
499         hd.user = trans.user();
500         hd.action = modified.name();
501         hd.target = TABLE;
502         hd.subject = subject ? override[1] : data.fullType();
503         if (memo) {
504             hd.memo = String.format("%s", override[0]);
505         } else {
506             hd.memo = String.format("%sd %s|%s|%s", modified.name(),data.fullType(),data.instance,data.action);
507         }
508         
509         if (modified==CRUD.delete) {
510             try {
511                 hd.reconstruct = data.bytify();
512             } catch (IOException e) {
513                 trans.error().log(e,"Could not serialize PermDAO.Data");
514             }
515         }
516         
517         if (historyDAO.create(trans, hd).status!=Status.OK) {
518             trans.error().log("Cannot log to History");
519         }
520         if (infoDAO.touch(trans, TABLE,data.invalidate(cache)).notOK()) {
521             trans.error().log("Cannot touch CacheInfo");
522         }
523     }
524 }
525