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