Merge "Add more junits to auth-batch"
[aaf/authz.git] / auth / auth-batch / src / main / java / org / onap / aaf / auth / batch / update / NotifyApprovals.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.batch.update;
23
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.io.PrintStream;
27 import java.util.ArrayList;
28 import java.util.Date;
29 import java.util.GregorianCalendar;
30 import java.util.List;
31 import java.util.Map.Entry;
32
33 import org.onap.aaf.auth.batch.Batch;
34 import org.onap.aaf.auth.batch.BatchPrincipal;
35 import org.onap.aaf.auth.batch.actions.Email;
36 import org.onap.aaf.auth.batch.actions.EmailPrint;
37 import org.onap.aaf.auth.batch.actions.Message;
38 import org.onap.aaf.auth.batch.helpers.Approval;
39 import org.onap.aaf.auth.batch.helpers.Future;
40 import org.onap.aaf.auth.dao.CassAccess;
41 import org.onap.aaf.auth.dao.cass.ApprovalDAO;
42 import org.onap.aaf.auth.dao.cass.FutureDAO;
43 import org.onap.aaf.auth.dao.cass.HistoryDAO;
44 import org.onap.aaf.auth.env.AuthzTrans;
45 import org.onap.aaf.auth.org.Organization;
46 import org.onap.aaf.auth.org.Organization.Identity;
47 import org.onap.aaf.auth.org.OrganizationException;
48 import org.onap.aaf.auth.org.OrganizationFactory;
49 import org.onap.aaf.cadi.Access;
50 import org.onap.aaf.cadi.CadiException;
51 import org.onap.aaf.cadi.config.RegistrationPropHolder;
52 import org.onap.aaf.misc.env.APIException;
53 import org.onap.aaf.misc.env.util.Chrono;
54
55 public class NotifyApprovals extends Batch {
56     private static final String LINE = "----------------------------------------------------------------";
57     private final HistoryDAO historyDAO;
58     private final ApprovalDAO apprDAO;
59     private final FutureDAO futureDAO;
60     private Email email;
61     private int maxEmails;
62     private final PrintStream ps;
63     private final AuthzTrans noAvg;
64
65     public NotifyApprovals(AuthzTrans trans) throws APIException, IOException, OrganizationException, CadiException {
66         super(trans.env());
67         Access access = trans.env().access();
68         RegistrationPropHolder rph = new RegistrationPropHolder(access, 0);
69         String guiURL = rph.replacements(access.getProperty(GUI_URL,"https://%P/gui"),"","");
70         noAvg = env.newTransNoAvg();
71         noAvg.setUser(new BatchPrincipal("batch:NotifyApprovals"));
72
73         historyDAO = new HistoryDAO(trans, cluster, CassAccess.KEYSPACE);
74         session = historyDAO.getSession(trans);
75         apprDAO = new ApprovalDAO(trans, historyDAO);
76         futureDAO = new FutureDAO(trans, historyDAO);
77         if (isDryRun()) {
78             email = new EmailPrint();
79             maxEmails=3;
80         } else {
81             email = new Email();
82             maxEmails = Integer.parseInt(trans.getProperty("MAX_EMAILS","3"));
83         }
84         email.subject("AAF Approval Notification (ENV: %s)",batchEnv);
85         email.preamble("AAF is the ONAP Authorization System." +
86                 "\n  Your approval is required, which you may enter on the following page:"
87                 + "\n\n\t%s/approve\n\n"
88                 ,guiURL);
89         email.signature("Sincerely,\nAAF Team\n");
90
91         Approval.load(trans, session, Approval.v2_0_17);
92         Future.load(trans, session, Future.v2_0_17); // Skip the Construct Data
93         
94         ps = new PrintStream(new FileOutputStream(logDir() + "/email"+Chrono.dateOnlyStamp()+".log",true));
95         ps.printf("### Approval Notify %s for %s%s\n",Chrono.dateTime(),batchEnv,dryRun?", DryRun":"");
96     }
97
98     @Override
99     protected void run(AuthzTrans trans) {
100         GregorianCalendar gc = new GregorianCalendar();
101         Date now = gc.getTime();
102         String today = Chrono.dateOnlyStamp(now);
103         gc.add(GregorianCalendar.MONTH, -1);
104         gc=null;
105
106
107         Message msg = new Message();
108         int emailCount = 0;
109         List<Approval> pending = new ArrayList<>();
110         boolean isOwner,isSupervisor;
111         for (Entry<String, List<Approval>> es : Approval.byApprover.entrySet()) {
112             isOwner = isSupervisor = false;
113             String approver = es.getKey();
114             if (approver.indexOf('@')<0) {
115                 approver += org.getRealm();
116             }
117             Date latestNotify=null, soonestExpire=null;
118             GregorianCalendar latest=new GregorianCalendar();
119             GregorianCalendar soonest=new GregorianCalendar();
120             pending.clear();
121             
122             for (Approval app : es.getValue()) {
123                 Future f = app.getTicket()==null?null:Future.data.get(app.getTicket());
124                 if (f==null) { // only Ticketed Approvals are valid.. the others are records.
125                     // Approvals without Tickets are no longer valid. 
126                     if ("pending".equals(app.getStatus())) {
127                         app.setStatus("lapsed");
128                         app.update(noAvg,apprDAO,dryRun); // obeys dryRun
129                     }
130                 } else {
131                     if ((soonestExpire==null && f.expires()!=null) || (soonestExpire!=null && f.expires()!=null && soonestExpire.before(f.expires()))) {
132                         soonestExpire=f.expires();
133                     }
134
135                     if ("pending".equals(app.getStatus())) {
136                         if (!isOwner) {
137                             isOwner = "owner".equals(app.getType());
138                         }
139                         if (!isSupervisor) {
140                             isSupervisor = "supervisor".equals(app.getType());
141                         }
142
143                         if ((latestNotify==null && app.getLast_notified()!=null) ||(latestNotify!=null && app.getLast_notified()!=null && latestNotify.before(app.getLast_notified()))) {
144                             latestNotify=app.getLast_notified();
145                         }
146                         pending.add(app);
147                     }
148                 }
149             }
150
151             if (!pending.isEmpty()) {
152                 boolean go = false;
153                 if (latestNotify==null) { // never notified... make it so
154                     go=true;
155                 } else {
156                     if (!today.equals(Chrono.dateOnlyStamp(latest))) { // already notified today
157                         latest.setTime(latestNotify);
158                         soonest.setTime(soonestExpire);
159                         int year;
160                         int days = soonest.get(GregorianCalendar.DAY_OF_YEAR)-latest.get(GregorianCalendar.DAY_OF_YEAR);
161                         days+=((year=soonest.get(GregorianCalendar.YEAR))-latest.get(GregorianCalendar.YEAR))*365 + 
162                                 (soonest.isLeapYear(year)?1:0);
163                         if (days<7) { // If Expirations get within a Week (or expired), notify everytime.
164                             go = true;
165                         }
166                     }
167                 }
168                 if (go) {
169                     if (maxEmails>emailCount++) {
170                         try {
171                             Organization org = OrganizationFactory.obtain(env, approver);
172                             Identity user = org.getIdentity(noAvg, approver);
173                             if (user==null) {
174                                 ps.printf("Invalid Identity: %s\n", approver);
175                             } else {
176                                 email.clear();
177                                 msg.clear();
178                                 email.addTo(user.email());
179                                 msg.line(LINE);
180                                 msg.line("Why are you receiving this Notification?\n");
181                                 if (isSupervisor) {
182                                     msg.line("%sYou are the supervisor of one or more employees who need access to tools which are protected by AAF.  " + 
183                                              "Your employees may ask for access to various tools and applications to do their jobs.  ASPR requires "
184                                              + "that you are notified and approve their requests. The details of each need is provided when you click "
185                                              + "on webpage above.\n",isOwner?"1) ":"");
186                                     msg.line("Your participation in this process fulfills the ASPR requirement to re-authorize users in roles on a regular basis.\n\n");
187                                 }
188                             
189                                 if (isOwner) {
190                                     msg.line("%sYou are the listed owner of one or more AAF Namespaces. ASPR requires that those responsible for "
191                                             + "applications and their access review them regularly for accuracy.  The AAF WIKI page for AT&T is https://wiki.web.att.com/display/aaf.  "
192                                             + "More info regarding questions of being a Namespace Owner is available at https://wiki.web.att.com/pages/viewpage.action?pageId=594741363\n",isSupervisor?"2) ":"");
193                                     msg.line("Additionally, Credentials attached to the Namespace must be renewed regularly.  While you may delegate certain functions to " + 
194                                              "Administrators within your Namespace, you are ultimately responsible to make sure credentials do not expire.\n");
195                                     msg.line("You may view the Namespaces you listed as Owner for in this AAF Env by viewing the following webpage:\n");
196                                     msg.line("   %s/ns\n\n",env.getProperty(GUI_URL));
197                                 
198                                 }
199                                 msg.line("  If you are unfamiliar with AAF, you might like to peruse the following links:"
200                                         + "\n\thttps://wiki.web.att.com/display/aaf/AAF+in+a+Nutshell"
201                                         + "\n\thttps://wiki.web.att.com/display/aaf/The+New+Person%%27s+Guide+to+AAF");
202                                 msg.line("\n  SPECIAL NOTE about SWM Management Groups: Understand that SWM management Groups correlate one-to-one to AAF Namespaces. "
203                                         + "(SWM uses AAF for the Authorization piece of Management Groups).  You may be assigned the SWM Management Group by asking "
204                                         + "directly, or through any of the above stated automated processes.  Auto-generated Namespaces typically look like 'com.att.44444.PROD' "
205                                         + "where '44444' is a MOTS ID, and 'PROD' is PROD|DEV|TEST, etc.  For your convenience, the MOTS link is http://ebiz.sbc.com/mots.\n");
206                                 msg.line("  Finally, realize that there are automated processes which create Machines and Resources via SWM, Kubernetes or other "
207                                         + "such tooling.  If you or your predecessor requested them, you were set as the owner of the AAF Namespace created during "
208                                         + "that process.\n");
209                                 msg.line("  For ALL QUESTIONS of why and how of SWM, and whether you or your reports can be removed, please contact SWM at "
210                                         + "https://wiki.web.att.com/display/swm/Support\n");
211
212                                 email.msg(msg);
213                                 email.exec(noAvg, org,"");
214                                 if (!isDryRun()) {
215                                     email.log(ps,"NotifyApprovals");
216                                     for (Approval app : pending) {
217                                         app.setLastNotified(now);
218                                         app.update(noAvg, apprDAO, dryRun);
219                                     }
220                                 }
221                             }
222                         } catch (OrganizationException e) {
223                             trans.info().log(e);
224                         }
225                     }
226                 }
227             }
228         }
229         trans.info().printf("%d emails sent for %s", emailCount,batchEnv);
230     }
231     
232     @Override
233     protected void _close(AuthzTrans trans) {
234         futureDAO.close(trans);
235         apprDAO.close(trans);
236         historyDAO.close(trans);
237         ps.close();
238     }
239 }