AT&T 2.0.19 Code drop, stage 2
[aaf/authz.git] / cadi / aaf / src / main / java / org / onap / aaf / cadi / persist / PersistFile.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.cadi.persist;
23
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.OutputStream;
27 import java.nio.file.Files;
28 import java.nio.file.NoSuchFileException;
29 import java.nio.file.Path;
30 import java.nio.file.Paths;
31 import java.nio.file.StandardCopyOption;
32 import java.nio.file.StandardOpenOption;
33 import java.nio.file.attribute.FileTime;
34 import java.nio.file.attribute.PosixFilePermission;
35 import java.nio.file.attribute.PosixFilePermissions;
36 import java.util.Set;
37
38 import javax.crypto.CipherInputStream;
39 import javax.crypto.CipherOutputStream;
40
41 import org.onap.aaf.cadi.Access;
42 import org.onap.aaf.cadi.CadiException;
43 import org.onap.aaf.cadi.Symm;
44 import org.onap.aaf.cadi.Access.Level;
45 import org.onap.aaf.cadi.Symm.Encryption;
46 import org.onap.aaf.cadi.client.Holder;
47 import org.onap.aaf.cadi.config.Config;
48 import org.onap.aaf.misc.env.APIException;
49 import org.onap.aaf.misc.rosetta.env.RosettaDF;
50
51 public class PersistFile {
52
53         private static final String HASH_NO_MATCH = "Hash does not match in Persistence";
54         
55         protected static Symm symm;
56         public Access access;
57         protected final Path tokenPath;
58         protected final String tokenDir;
59         private static final boolean isWindows = System.getProperty("os.name").startsWith("Windows");
60         
61         public PersistFile(Access access, String sub_dir) throws CadiException, APIException {
62                 this.access = access;
63                 tokenPath = Paths.get(access.getProperty(Config.CADI_TOKEN_DIR,"tokens"), sub_dir);
64                 try {
65                         if(!Files.exists(tokenPath)) {
66                                 if(isWindows) {
67                                         // Sorry Windows users, you need to secure your own paths
68                                         Files.createDirectories(tokenPath);
69                                 } else {
70                                         Set<PosixFilePermission> spfp = PosixFilePermissions.fromString("rwxr-x---");
71                                         Files.createDirectories(tokenPath,PosixFilePermissions.asFileAttribute(spfp));
72                                 }
73                         }
74                         tokenDir=tokenPath.toRealPath().toString();
75                 } catch (IOException e) {
76                         throw new CadiException(e);
77                 }
78                 synchronized(HASH_NO_MATCH) {
79                         if(symm==null) {
80                                 symm = Symm.obtain(access);
81                         }
82                 }
83         }
84
85         public<T> Path writeDisk(final RosettaDF<T> df, final T t, final byte[] cred, final String filename, final long expires) throws CadiException {
86                 return writeDisk(df,t,cred,Paths.get(tokenDir,filename),expires);
87         }
88
89         public<T> Path writeDisk(final RosettaDF<T> df, final T t, final byte[] cred, final Path target, final long expires) throws CadiException {
90                 // Make sure File is completely written before making accessible on disk... avoid corruption.
91                 try {
92                         Path tpath = Files.createTempFile(tokenPath,target.getFileName().toString(), ".tmp");
93                         final OutputStream dos = Files.newOutputStream(tpath, StandardOpenOption.CREATE,StandardOpenOption.WRITE);
94                                 try {
95                                 // Write Expires so that we can read unencrypted.
96                                 for(int i=0;i<Long.SIZE;i+=8) {
97                                         dos.write((byte)((expires>>i)&0xFF));
98                                 }
99
100                                 symm.exec(new Symm.SyncExec<Void>() {
101                                         @Override
102                                         public Void exec(Encryption enc) throws Exception {
103                                                 CipherOutputStream os = enc.outputStream(dos, true);
104                                                 try {
105                                                         int size = cred==null?0:cred.length;
106                                                         for(int i=0;i<Integer.SIZE;i+=8) {
107                                                                 os.write((byte)((size>>i)&0xFF));
108                                                         }
109                                                         if(cred!=null) {
110                                                                 os.write(cred);
111                                                         }
112                                                         df.newData().load(t).to(os);
113                                                 } finally {
114                                                         // Note: Someone on the Web noticed that using a DataOutputStream would not full close out without a flush first, 
115                                                         // leaving files open.
116                                                         try {
117                                                                 os.flush();
118                                                         } catch (IOException e) {
119                                                                 access.log(Level.INFO, "Note: Caught Exeption while flushing CipherStream.  Handled.");
120                                                         }
121                                                         try {
122                                                                 os.close();
123                                                         } catch (IOException e) {
124                                                                 access.log(Level.INFO, "Note: Caught Exeption while closing CipherStream.  Handled.");
125                                                         }
126                                                 }
127                                                 return null;
128                                         }
129                                 });
130                         } catch(Exception e) {
131                                 throw new CadiException(e);
132                         } finally {
133                                 dos.close();
134                         }
135                         return Files.move(tpath, target, StandardCopyOption.ATOMIC_MOVE,StandardCopyOption.REPLACE_EXISTING);
136                 } catch (IOException e) {
137                         throw new CadiException(e);
138                 }
139
140         }
141
142         public <T> T readDisk(final RosettaDF<T> df, final byte[] cred, final String filename,final Holder<Path> hp, final Holder<Long> hl) throws CadiException {
143                 if(hp.get()==null) {
144                         hp.set(Paths.get(tokenDir,filename));
145                 }
146                 return readDisk(df,cred,hp.get(),hl);
147         }
148         
149         public <T> T readDisk(final RosettaDF<T> df, final byte[] cred, final Path target, final Holder<Long> hexpired) throws CadiException {
150                 // Try from Disk
151                 T t = null;
152                 if(Files.exists(target)) {
153                         try {
154                                 final InputStream is = Files.newInputStream(target,StandardOpenOption.READ);
155                                 try {
156                                         // Read Expired unencrypted
157                                         long exp=0;
158                                         for(int i=0;i<Long.SIZE;i+=8) {
159                                                 exp |= ((long)is.read()<<i);
160                                         }
161                                         hexpired.set(exp);
162                                 
163                                         t = symm.exec(new Symm.SyncExec<T>() {
164                                                 @Override
165                                                 public T exec(Encryption enc) throws Exception {
166                                                         CipherInputStream dis = enc.inputStream(is,false);
167                                                         try {
168                                                                 int size=0;
169                                                                 for(int i=0;i<Integer.SIZE;i+=8) {
170                                                                         size |= ((int)dis.read()<<i);
171                                                                 }
172                                                                 if(size>256) {
173                                                                         throw new CadiException("Invalid size in Token Persistence");
174                                                                 } else if(cred!=null && size!=cred.length) {
175                                                                         throw new CadiException(HASH_NO_MATCH);
176                                                                 }
177                                                                 byte[] array = new byte[size];
178                                                                 dis.read(array);
179                                                                 for(int i=0;i<size;++i) {
180                                                                         if(cred[i]!=array[i]) {
181                                                                                 throw new CadiException(HASH_NO_MATCH);
182                                                                         }
183                                                                 }
184                                                                 
185                                                                 return df.newData().load(dis).asObject();
186                                                         } finally {
187                                                                 dis.close();
188                                                         }
189                                                 }
190                                         });
191                                 } finally {
192                                         is.close();
193                                 }
194                         } catch (NoSuchFileException e) { 
195                                 return t;
196                         } catch (Exception e) {
197                                 throw new CadiException(e);
198                         }
199                 }
200                 return t;
201         }
202         
203         public long readExpiration(final Path target) throws CadiException {
204                 long exp=0L;
205                 if(Files.exists(target)) {
206                         try {
207                                 final InputStream is = Files.newInputStream(target,StandardOpenOption.READ);
208                                 try {
209                                         for(int i=0;i<Long.SIZE;i+=8) {
210                                                 exp |= ((long)is.read()<<i);
211                                         }
212                                 } finally {
213                                         is.close();
214                                 }
215                                 return exp;
216                         } catch (Exception e) {
217                                 throw new CadiException(e);
218                         }
219                 }
220                 return exp;
221         }
222
223         public void deleteFromDisk(Path path) {
224                 try {
225                         Files.deleteIfExists(path);
226                 } catch (IOException e) {
227                         access.log(Level.ERROR, e);
228                 }
229         }
230
231         public void deleteFromDisk(String token) {
232                 Path tpath = Paths.get(tokenDir,token);
233                 try {
234                         Files.deleteIfExists(tpath);
235                 } catch (IOException e) {
236                         access.log(Level.ERROR, e);
237                 }
238         }
239
240         public Path getPath(String filename) {
241                 return Paths.get(tokenDir,filename);
242         }
243         
244         public FileTime getFileTime(String filename, Holder<Path> hp) throws IOException {
245                 Path p = hp.get();
246                 if(p==null) {
247                         hp.set(p=Paths.get(tokenDir,filename));
248                 }
249                 return Files.getLastModifiedTime(p);
250         }
251
252 }