View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.user2;
20  
21  import java.io.File;
22  import java.io.FileNotFoundException;
23  import java.io.FileOutputStream;
24  import java.io.IOException;
25  import java.io.OutputStream;
26  import java.text.MessageFormat;
27  import java.util.ArrayList;
28  import java.util.Arrays;
29  import java.util.Collection;
30  import java.util.Collections;
31  import java.util.HashSet;
32  import java.util.List;
33  import java.util.Locale;
34  import java.util.Optional;
35  import java.util.Set;
36  
37  import org.apache.logging.log4j.LogManager;
38  import org.apache.logging.log4j.Logger;
39  import org.jdom2.Document;
40  import org.jdom2.Element;
41  import org.jdom2.output.Format;
42  import org.jdom2.output.XMLOutputter;
43  import org.mycore.common.MCRConstants;
44  import org.mycore.common.MCRException;
45  import org.mycore.common.MCRSession;
46  import org.mycore.common.MCRSessionMgr;
47  import org.mycore.common.config.MCRConfiguration2;
48  import org.mycore.common.content.MCRFileContent;
49  import org.mycore.common.xml.MCRXMLParserFactory;
50  import org.mycore.datamodel.classifications2.MCRLabel;
51  import org.mycore.frontend.cli.MCRAbstractCommands;
52  import org.mycore.frontend.cli.annotation.MCRCommand;
53  import org.mycore.frontend.cli.annotation.MCRCommandGroup;
54  import org.mycore.user2.utils.MCRRoleTransformer;
55  import org.mycore.user2.utils.MCRUserTransformer;
56  import org.xml.sax.SAXParseException;
57  
58  /**
59   * This class provides a set of commands for the org.mycore.user2 management which can be used by the command line
60   * interface.
61   *
62   * @author Thomas Scheffler (yagee)
63   */
64  @MCRCommandGroup(
65      name = "User Commands")
66  public class MCRUserCommands extends MCRAbstractCommands {
67      /** The logger */
68      private static Logger LOGGER = LogManager.getLogger(MCRUserCommands.class.getName());
69  
70      private static final String SYSTEM = MCRConfiguration2.getStringOrThrow("MCR.CommandLineInterface.SystemName")
71          + ":";
72  
73      /**
74       * This command changes the user of the session context to a new user.
75       *
76       * @param user
77       *            the new user ID
78       * @param password
79       *            the password of the new user
80       */
81      @MCRCommand(
82          syntax = "change to user {0} with {1}",
83          help = "Changes to the user {0} with the given password {1}.",
84          order = 10)
85      public static void changeToUser(String user, String password) {
86          MCRSession session = MCRSessionMgr.getCurrentSession();
87          System.out.println(SYSTEM + " The old user ID is " + session.getUserInformation().getUserID());
88          if (MCRUserManager.login(user, password) != null) {
89              System.out.println(SYSTEM + " The new user ID is " + session.getUserInformation().getUserID());
90          } else {
91              LOGGER.warn("Wrong password, no changes of user ID in session context!");
92          }
93      }
94  
95      /**
96       * This command changes the user of the session context to a new user.
97       *
98       * @param user
99       *            the new user ID
100      */
101     @MCRCommand(
102         syntax = "login {0}",
103         help = "Starts the login dialog for the user {0}.",
104         order = 20)
105     public static void login(String user) {
106         char[] password = {};
107         do {
108             password = System.console().readPassword("{0} Enter password for user {1} :> ", SYSTEM, user);
109         } while (password.length == 0);
110 
111         changeToUser(user, String.valueOf(password));
112     }
113 
114     /**
115      * This method initializes the user and role system an creates a superuser with values set in
116      * mycore.properties.private As 'super' default, if no properties were set, mcradmin with password mycore will be
117      * used.
118      */
119     @MCRCommand(
120         syntax = "init superuser",
121         help = "Initializes the user system. This command runs only if the user database does not exist.",
122         order = 30)
123     public static List<String> initSuperuser() {
124         final String suser = MCRConfiguration2.getStringOrThrow("MCR.Users.Superuser.UserName");
125         final String spasswd = MCRConfiguration2.getStringOrThrow("MCR.Users.Superuser.UserPasswd");
126         final Optional<String> semail = MCRConfiguration2.getString("MCR.Users.Superuser.UserEmail");
127         final String srole = MCRConfiguration2.getStringOrThrow("MCR.Users.Superuser.GroupName");
128 
129         if (MCRUserManager.exists(suser)) {
130             LOGGER.error("The superuser already exists!");
131             return null;
132         }
133 
134         // the superuser role
135         try {
136             Set<MCRLabel> labels = new HashSet<>();
137             labels.add(new MCRLabel("en", "The superuser role", null));
138 
139             MCRRole mcrRole = new MCRRole(srole, labels);
140             MCRRoleManager.addRole(mcrRole);
141         } catch (Exception e) {
142             throw new MCRException("Can't create the superuser role.", e);
143         }
144 
145         LOGGER.info("The role {} is installed.", srole);
146 
147         // the superuser
148         try {
149             MCRUser mcrUser = new MCRUser(suser);
150             mcrUser.setRealName("Superuser");
151             semail.ifPresent(mcrUser::setEMail);
152             mcrUser.assignRole(srole);
153             MCRUserManager.updatePasswordHashToSHA256(mcrUser, spasswd);
154             MCRUserManager.createUser(mcrUser);
155         } catch (Exception e) {
156             throw new MCRException("Can't create the superuser.", e);
157         }
158 
159         LOGGER.info("The user {} with password {} is installed.", suser, spasswd);
160         return Collections.singletonList("change to user " + suser + " with " + spasswd);
161     }
162 
163     /**
164      * This method invokes {@link MCRRoleManager#deleteRole(String)} and permanently removes a role from the system.
165      *
166      * @param roleID
167      *            the ID of the role which will be deleted
168      */
169     @MCRCommand(
170         syntax = "delete role {0}",
171         help = "Deletes the role {0} from the user system, but only if it has no user assigned.",
172         order = 80)
173     public static void deleteRole(String roleID) {
174         MCRRoleManager.deleteRole(roleID);
175     }
176 
177     /**
178      * Exports a single role to the specified directory.
179      * @throws FileNotFoundException if target directory does not exist
180      */
181     @MCRCommand(
182         syntax = "export role {0} to directory {1}",
183         help = "Export the role {0} to the directory {1}. The filename will be {0}.xml")
184     public static void exportRole(String roleID, String directory) throws IOException {
185         MCRRole mcrRole = MCRRoleManager.getRole(roleID);
186         File targetFile = new File(directory, roleID + ".xml");
187         try (FileOutputStream fout = new FileOutputStream(targetFile)) {
188             XMLOutputter xout = new XMLOutputter(Format.getPrettyFormat().setEncoding(MCRConstants.DEFAULT_ENCODING));
189             xout.output(MCRRoleTransformer.buildExportableXML(mcrRole), fout);
190         }
191     }
192 
193     /**
194      * Loads XML from a user and looks for roles currently not present in the system and creates them.
195      *
196      * @param fileName
197      *            a valid user XML file
198      */
199     @MCRCommand(
200         syntax = "import role from file {0}",
201         help = "Imports a role from file, if that role does not exist",
202         order = 90)
203     public static void addRole(String fileName) throws SAXParseException, IOException {
204         LOGGER.info("Reading file {} ...", fileName);
205         Document doc = MCRXMLParserFactory.getNonValidatingParser().parseXML(new MCRFileContent(fileName));
206         MCRRole role = MCRRoleTransformer.buildMCRRole(doc.getRootElement());
207         if (MCRRoleManager.getRole(role.getName()) == null) {
208             MCRRoleManager.addRole(role);
209         } else {
210             LOGGER.info("Role {} does already exist.", role.getName());
211         }
212     }
213 
214     /**
215      * This method invokes MCRUserMgr.deleteUser() and permanently removes a user from the system.
216      *
217      * @param userID
218      *            the ID of the user which will be deleted
219      */
220     @MCRCommand(
221         syntax = "delete user {0}",
222         help = "Delete the user {0}.",
223         order = 110)
224     public static void deleteUser(String userID) throws Exception {
225         MCRUserManager.deleteUser(userID);
226     }
227 
228     /**
229      * This method invokes MCRUserMgr.enableUser() that enables a user
230      *
231      * @param userID
232      *            the ID of the user which will be enabled
233      */
234     @MCRCommand(
235         syntax = "enable user {0}",
236         help = "Enables the user for the access.",
237         order = 60)
238     public static void enableUser(String userID) throws Exception {
239         MCRUser mcrUser = MCRUserManager.getUser(userID);
240         mcrUser.enableLogin();
241         MCRUserManager.updateUser(mcrUser);
242     }
243 
244     /**
245      * A given XML file containing user data with cleartext passwords must be converted prior to loading the user data
246      * into the system. This method reads all user objects in the given XML file, encrypts the passwords and writes them
247      * back to a file with name original-file-name_encrypted.xml.
248      *
249      * @param oldFile
250      *            the filename of the user data input
251      * @param newFile
252      *            the filename of the user data output (encrypted passwords)
253      */
254     @MCRCommand(
255         syntax = "encrypt passwords in user xml file {0} to file {1}",
256         help = "A migration tool to change old plain text password entries to encrpted entries.",
257         order = 40)
258     public static void encryptPasswordsInXMLFile(String oldFile, String newFile) throws SAXParseException, IOException {
259         File inputFile = getCheckedFile(oldFile);
260         if (inputFile == null) {
261             return;
262         }
263         LOGGER.info("Reading file {} ...", inputFile.getAbsolutePath());
264 
265         Document doc = MCRXMLParserFactory.getNonValidatingParser().parseXML(new MCRFileContent(inputFile));
266         Element rootelm = doc.getRootElement();
267         MCRUser mcrUser = MCRUserTransformer.buildMCRUser(rootelm);
268 
269         if (mcrUser == null) {
270             throw new MCRException("These data do not correspond to a user.");
271         }
272 
273         MCRUserManager.updatePasswordHashToSHA256(mcrUser, mcrUser.getPassword());
274 
275         FileOutputStream outFile = new FileOutputStream(newFile);
276         saveToXMLFile(mcrUser, outFile);
277     }
278 
279     /**
280      * This method invokes MCRUserMgr.disableUser() that disables a user
281      *
282      * @param userID
283      *            the ID of the user which will be enabled
284      */
285     @MCRCommand(
286         syntax = "disable user {0}",
287         help = "Disables access of the user {0}",
288         order = 70)
289     public static void disableUser(String userID) throws Exception {
290         MCRUser mcrUser = MCRUserManager.getUser(userID);
291         mcrUser.disableLogin();
292         MCRUserManager.updateUser(mcrUser);
293     }
294 
295     /**
296      * This method invokes MCRUserMgr.getAllUserIDs() and retrieves a ArrayList of all users stored in the persistent
297      * datastore.
298      */
299     @MCRCommand(
300         syntax = "list all users",
301         help = "Lists all users.",
302         order = 160)
303     public static void listAllUsers() throws Exception {
304         List<MCRUser> users = MCRUserManager.listUsers(null, null, null, null);
305 
306         for (MCRUser uid : users) {
307             listUser(uid);
308         }
309     }
310 
311     /**
312      * This method invokes {@link MCRRoleManager#listSystemRoles()} and retrieves a list of all roles stored in the
313      * persistent datastore.
314      */
315     @MCRCommand(
316         syntax = "list all roles",
317         help = "List all roles.",
318         order = 140)
319     public static void listAllRoles() throws Exception {
320         List<MCRRole> roles = MCRRoleManager.listSystemRoles();
321 
322         for (MCRRole role : roles) {
323             listRole(role);
324         }
325     }
326 
327     /**
328      * This command takes a userID and file name as a parameter, retrieves the user from MCRUserMgr as JDOM document and
329      * export this to the given file.
330      *
331      * @param userID
332      *            ID of the user to be saved
333      * @param filename
334      *            Name of the file to store the exported user
335      */
336     @MCRCommand(
337         syntax = "export user {0} to file {1}",
338         help = "Exports the data of user {0} to the file {1}.",
339         order = 180)
340     public static void exportUserToFile(String userID, String filename) throws IOException {
341         MCRUser user = MCRUserManager.getUser(userID);
342         if (user.getSystemRoleIDs().isEmpty()) {
343             LOGGER.warn("User {} has not any system roles.", user.getUserID());
344         }
345         FileOutputStream outFile = new FileOutputStream(filename);
346         LOGGER.info("Writing to file {} ...", filename);
347         saveToXMLFile(user, outFile);
348     }
349 
350     @MCRCommand(
351         syntax = "export all users to directory {0}",
352         help = "Exports the data of all users to the directory {0}.")
353     public static List<String> exportAllUserToDirectory(String directory) throws IOException {
354         File dir = new File(directory);
355         if (!dir.exists() || !dir.isDirectory()) {
356             throw new MCRException("Directory does not exist: " + dir.getAbsolutePath());
357         }
358         List<MCRUser> users = MCRUserManager.listUsers(null, null, null, null);
359         ArrayList<String> commands = new ArrayList<>(users.size());
360         for (MCRUser user : users) {
361             File userFile = new File(dir, user.getUserID() + ".xml");
362             commands.add("export user " + user.getUserID() + " to file " + userFile.getAbsolutePath());
363         }
364         return commands;
365     }
366 
367     @MCRCommand(
368         syntax = "import all users from directory {0}",
369         help = "Imports all users from directory {0}.")
370     public static List<String> importAllUsersFromDirectory(String directory) throws FileNotFoundException {
371         return batchLoadFromDirectory("import user from file", directory);
372     }
373 
374     @MCRCommand(
375         syntax = "update all users from directory {0}",
376         help = "Updates all users from directory {0}.")
377     public static List<String> updateAllUsersFromDirectory(String directory) throws FileNotFoundException {
378         return batchLoadFromDirectory("update user from file", directory);
379     }
380 
381     public static List<String> batchLoadFromDirectory(String cmd, String directory) throws FileNotFoundException {
382         File dir = new File(directory);
383         if (!dir.isDirectory()) {
384             throw new FileNotFoundException(dir.getAbsolutePath() + " is not a directory.");
385         }
386         File[] listFiles = dir
387             .listFiles(pathname -> pathname.isFile() && pathname.getName().endsWith(".xml"));
388         if (listFiles.length == 0) {
389             LOGGER.warn("Did not find any user files in {}", dir.getAbsolutePath());
390             return null;
391         }
392         Arrays.sort(listFiles);
393         ArrayList<String> cmds = new ArrayList<>(listFiles.length);
394         for (File file : listFiles) {
395             cmds.add(new MessageFormat("{0} {1}", Locale.ROOT).format(new Object[] { cmd, file.getAbsolutePath() }));
396         }
397         return cmds;
398     }
399 
400     /**
401      * This command takes a file name as a parameter, creates the MCRUser instances stores it in the database if it does
402      * not exists.
403      *
404      * @param filename
405      *            Name of the file to import user from
406      */
407     @MCRCommand(
408         syntax = "import user from file {0}",
409         help = "Imports a user from file {0}.")
410     public static void importUserFromFile(String filename) throws SAXParseException, IOException {
411         MCRUser user = getMCRUserFromFile(filename);
412         if (MCRUserManager.exists(user.getUserName(), user.getRealmID())) {
413             throw new MCRException("User already exists: " + user.getUserID());
414         }
415         MCRUserManager.createUser(user);
416     }
417 
418     /**
419      * This method invokes MCRUserMgr.retrieveUser() and then works with the retrieved user object to change the
420      * password.
421      *
422      * @param userID
423      *            the ID of the user for which the password will be set
424      */
425     @MCRCommand(
426         syntax = "set password for user {0} to {1}",
427         help = "Sets a new password for the user. You must be this user or you must have administrator access.",
428         order = 50)
429     public static void setPassword(String userID, String password) throws MCRException {
430         MCRUser user = MCRUserManager.getUser(userID);
431         MCRUserManager.updatePasswordHashToSHA256(user, password);
432         MCRUserManager.updateUser(user);
433     }
434 
435     /**
436      * This method invokes {@link MCRRoleManager#getRole(String)} and then works with the retrieved role object to get
437      * an XML-Representation.
438      *
439      * @param roleID
440      *            the ID of the role for which the XML-representation is needed
441      */
442     @MCRCommand(
443         syntax = "list role {0}",
444         help = "Lists the role {0}.",
445         order = 150)
446     public static void listRole(String roleID) throws MCRException {
447         MCRRole role = MCRRoleManager.getRole(roleID);
448         listRole(role);
449     }
450 
451     public static void listRole(MCRRole role) {
452         StringBuilder sb = new StringBuilder();
453         sb.append("       role=").append(role.getName());
454         for (MCRLabel label : role.getLabels()) {
455             sb.append("\n         ").append(label);
456         }
457         Collection<String> userIds = MCRRoleManager.listUserIDs(role);
458         for (String userId : userIds) {
459             sb.append("\n          user assigned to role=").append(userId);
460         }
461         LOGGER.info(sb.toString());
462     }
463 
464     /**
465      * This method invokes MCRUserMgr.retrieveUser() and then works with the retrieved user object to get an
466      * XML-Representation.
467      *
468      * @param userID
469      *            the ID of the user for which the XML-representation is needed
470      */
471     @MCRCommand(
472         syntax = "list user {0}",
473         help = "Lists the user {0}.",
474         order = 170)
475     public static void listUser(String userID) throws MCRException {
476         MCRUser user = MCRUserManager.getUser(userID);
477         listUser(user);
478     }
479 
480     public static void listUser(MCRUser user) {
481         StringBuilder sb = new StringBuilder("\n");
482         sb.append("       user=").append(user.getUserName()).append("   real name=").append(user.getRealName())
483             .append('\n').append("   loginAllowed=").append(user.loginAllowed()).append('\n');
484         List<String> roles = new ArrayList<>(user.getSystemRoleIDs());
485         roles.addAll(user.getExternalRoleIDs());
486         for (String rid : roles) {
487             sb.append("          assigned to role=").append(rid).append('\n');
488         }
489         LOGGER.info(sb.toString());
490     }
491 
492     /**
493      * Check the file name
494      *
495      * @param filename
496      *            the filename of the user data input
497      * @return true if the file name is okay
498      */
499     private static File getCheckedFile(String filename) {
500         if (!filename.endsWith(".xml")) {
501             LOGGER.warn("{} ignored, does not end with *.xml", filename);
502 
503             return null;
504         }
505 
506         File file = new File(filename);
507         if (!file.isFile()) {
508             LOGGER.warn("{} ignored, is not a file.", filename);
509 
510             return null;
511         }
512 
513         return file;
514     }
515 
516     /**
517      * This method invokes MCRUserMgr.createUser() with data from a file.
518      *
519      * @param filename
520      *            the filename of the user data input
521      */
522     public static void createUserFromFile(String filename) throws SAXParseException, IOException {
523         MCRUser user = getMCRUserFromFile(filename);
524         MCRUserManager.createUser(user);
525     }
526 
527     /**
528      * This method invokes MCRUserMgr.updateUser() with data from a file.
529      *
530      * @param filename
531      *            the filename of the user data input
532      * @throws SAXParseException
533      *             if file could not be parsed
534      */
535     @MCRCommand(
536         syntax = "update user from file {0}",
537         help = "Updates a user from file {0}.",
538         order = 200)
539     public static void updateUserFromFile(String filename) throws SAXParseException, IOException {
540         MCRUser user = getMCRUserFromFile(filename);
541         MCRUserManager.updateUser(user);
542     }
543 
544     private static MCRUser getMCRUserFromFile(String filename) throws SAXParseException, IOException {
545         File inputFile = getCheckedFile(filename);
546         if (inputFile == null) {
547             return null;
548         }
549         LOGGER.info("Reading file {} ...", inputFile.getAbsolutePath());
550         Document doc = MCRXMLParserFactory.getNonValidatingParser().parseXML(new MCRFileContent(inputFile));
551         return MCRUserTransformer.buildMCRUser(doc.getRootElement());
552     }
553 
554     /**
555      * This method adds a user as a member to a role
556      *
557      * @param userID
558      *            the ID of the user which will be a member of the role represented by roleID
559      * @param roleID
560      *            the ID of the role to which the user with ID mbrUserID will be added
561      */
562     @MCRCommand(
563         syntax = "assign user {0} to role {1}",
564         help = "Adds a user {0} as secondary member in the role {1}.",
565         order = 120)
566     public static void assignUserToRole(String userID, String roleID) throws MCRException {
567         try {
568             MCRUser user = MCRUserManager.getUser(userID);
569             user.assignRole(roleID);
570             MCRUserManager.updateUser(user);
571         } catch (Exception e) {
572             throw new MCRException("Error while assigning " + userID + " to role " + roleID + ".", e);
573         }
574     }
575 
576     /**
577      * This method removes a member user from a role
578      *
579      * @param userID
580      *            the ID of the user which will be removed from the role represented by roleID
581      * @param roleID
582      *            the ID of the role from which the user with ID mbrUserID will be removed
583      */
584     @MCRCommand(
585         syntax = "unassign user {0} from role {1}",
586         help = "Removes the user {0} as secondary member from the role {1}.",
587         order = 130)
588     public static void unassignUserFromRole(String userID, String roleID) throws MCRException {
589         try {
590             MCRUser user = MCRUserManager.getUser(userID);
591             user.unassignRole(roleID);
592             MCRUserManager.updateUser(user);
593         } catch (Exception e) {
594             throw new MCRException("Error while unassigning " + userID + " from role " + roleID + ".", e);
595         }
596     }
597 
598     /**
599      * This method just saves a JDOM document to a file automatically closes {@link OutputStream}.
600      *
601      * @param mcrUser
602      *            the JDOM XML document to be printed
603      * @param outFile
604      *            a FileOutputStream object for the output
605      * @throws IOException
606      *             if output file can not be closed
607      */
608     private static void saveToXMLFile(MCRUser mcrUser, FileOutputStream outFile) throws MCRException, IOException {
609         // Create the output
610         XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat().setEncoding(MCRConstants.DEFAULT_ENCODING));
611 
612         try {
613             outputter.output(MCRUserTransformer.buildExportableXML(mcrUser), outFile);
614         } catch (Exception e) {
615             throw new MCRException("Error while save XML to file: " + e.getMessage());
616         } finally {
617             outFile.close();
618         }
619     }
620 }