>From cfead8f46972848265ac3be0a6ec01eed51da3a7 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Wed, 23 Jul 2014 02:40:07 -0400 Subject: [PATCH 09/10] Update pki-profile CLI commands to work with "raw" format Update CLI commands for working with the (now LDAP-based) profiles in the same format as was used by the files, by way of the --raw option. Also add the "edit" command to interactively edit a profile. --- .../dogtagpki/server/ca/rest/ProfileService.java | 160 ++++++++++++++++++--- .../netscape/certsrv/profile/ProfileClient.java | 15 ++ .../netscape/certsrv/profile/ProfileResource.java | 20 ++- .../netscape/cmstools/profile/ProfileAddCLI.java | 37 ++++- .../com/netscape/cmstools/profile/ProfileCLI.java | 1 + .../netscape/cmstools/profile/ProfileEditCLI.java | 102 +++++++++++++ .../cmstools/profile/ProfileModifyCLI.java | 35 ++++- .../netscape/cmstools/profile/ProfileShowCLI.java | 31 ++-- 8 files changed, 359 insertions(+), 42 deletions(-) create mode 100644 base/java-tools/src/com/netscape/cmstools/profile/ProfileEditCLI.java diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/ProfileService.java b/base/ca/src/org/dogtagpki/server/ca/rest/ProfileService.java index d3f08b270fd66154da880d47be30ea48716b75bd..2017a6c426cc3e49e76bc4e6b52de721fd8999dd 100644 --- a/base/ca/src/org/dogtagpki/server/ca/rest/ProfileService.java +++ b/base/ca/src/org/dogtagpki/server/ca/rest/ProfileService.java @@ -18,6 +18,8 @@ package org.dogtagpki.server.ca.rest; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.URI; import java.security.Principal; @@ -27,6 +29,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Properties; import java.util.Vector; import javax.servlet.http.HttpServletRequest; @@ -163,9 +166,7 @@ public class ProfileService extends PKIService implements ProfileResource { return createOKResponse(infos); } - @Override - public Response retrieveProfile(String profileId) throws ProfileNotFoundException { - ProfileData data = null; + private IProfile getProfile(String profileId) throws ProfileNotFoundException { boolean visibleOnly = true; if (profileId == null) { @@ -185,24 +186,12 @@ public class ProfileService extends PKIService implements ProfileResource { visibleOnly = false; } - Enumeration profileIds = ps.getProfileIds(); - - IProfile profile = null; - if (profileIds != null) { - while (profileIds.hasMoreElements()) { - String id = profileIds.nextElement(); - - if (id.equals(profileId)) { - - try { - profile = ps.getProfile(profileId); - } catch (EProfileException e) { - e.printStackTrace(); - throw new ProfileNotFoundException(profileId); - } - break; - } - } + IProfile profile; + try { + profile = ps.getProfile(profileId); + } catch (EProfileException e) { + e.printStackTrace(); + throw new ProfileNotFoundException(profileId); } if (profile == null) { @@ -213,6 +202,14 @@ public class ProfileService extends PKIService implements ProfileResource { throw new ProfileNotFoundException(profileId); } + return profile; + } + + @Override + public Response retrieveProfile(String profileId) throws ProfileNotFoundException { + IProfile profile = getProfile(profileId); + + ProfileData data = null; try { data = createProfileData(profileId); } catch (EBaseException e) { @@ -228,6 +225,19 @@ public class ProfileService extends PKIService implements ProfileResource { return createOKResponse(data); } + @Override + public Response retrieveProfileRaw(String profileId) + throws ProfileNotFoundException { + IProfile profile = getProfile(profileId); + ByteArrayOutputStream data = new ByteArrayOutputStream(); + // add profileId and classId "virtual" properties + profile.getConfigStore().put("profileId", profileId); + profile.getConfigStore().put("classId", ps.getProfileClassId(profileId)); + profile.getConfigStore().save(data, null); + return createOKResponse(data.toByteArray()); + } + + public ProfileData createProfileData(String profileId) throws EBaseException { IProfile profile; @@ -514,6 +524,75 @@ public class ProfileService extends PKIService implements ProfileResource { } @Override + public Response createProfileRaw(byte[] data) { + if (data == null) { + CMS.debug("createProfileRaw: profile data is null"); + throw new BadRequestException("Unable to create profile: Invalid profile data."); + } + + if (ps == null) { + CMS.debug("createProfile: ps is null"); + throw new PKIException("Error creating profile. Profile Service not available"); + } + + Map auditParams = new LinkedHashMap(); + String profileId = null; + String classId = null; + try { + // load data as properties and read profileId and classId + Properties properties = new Properties(); + properties.load(new ByteArrayInputStream(data)); + profileId = properties.getProperty("profileId"); + classId = properties.getProperty("classId"); + } catch (IOException e) { + throw new BadRequestException("Could not parse raw profile data."); + } + if (profileId == null) { + throw new BadRequestException("Profile data did not contain profileId attribute."); + } + if (classId == null) { + throw new BadRequestException("Profile data did not contain classId attribute."); + } + + try { + IProfile profile = ps.getProfile(profileId); + if (profile != null) { + throw new BadRequestException("Profile already exists"); + } + + auditParams.put("class_id", classId); + + IPluginInfo info = registry.getPluginInfo("profile", classId); + + profile = ps.createProfile(profileId, classId, info.getClassName()); + profile.getConfigStore().commit(false); + profile.getConfigStore().load(new ByteArrayInputStream(data)); + ps.disableProfile(profileId); + + auditProfileChange( + ScopeDef.SC_PROFILE_RULES, + OpDef.OP_ADD, + profileId, + ILogger.SUCCESS, + auditParams); + + return createCreatedResponse(data, uriInfo.getAbsolutePath()); + } catch (EBaseException | IOException e) { + CMS.debug("createProfile: error in creating profile: " + e); + e.printStackTrace(); + + auditProfileChange( + ScopeDef.SC_PROFILE_RULES, + OpDef.OP_ADD, + profileId, + ILogger.FAILURE, + auditParams); + + throw new PKIException("Error in creating profile"); + } + } + + @Override public Response modifyProfile(String profileId, ProfileData data) { if (profileId == null) { CMS.debug("modifyProfile: invalid request. profileId is null"); @@ -550,6 +629,45 @@ public class ProfileService extends PKIService implements ProfileResource { } } + @Override + public Response modifyProfileRaw(String profileId, byte[] data) { + if (profileId == null) { + CMS.debug("modifyProfile: invalid request. profileId is null"); + throw new BadRequestException("Unable to modify profile: Invalid Profile Id"); + } + + if (data == null) { + CMS.debug("modifyProfile: invalid request. data is null"); + throw new BadRequestException("Unable to modify profile: Invalid profile data"); + } + + if (ps == null) { + CMS.debug("modifyProfile: ps is null"); + throw new PKIException("Error modifying profile. Profile Service not available"); + } + + if (ps.isProfileEnable(profileId)) { + throw new BadRequestException("Cannot change profile data. Profile must be disabled"); + } + + try { + IProfile profile = ps.getProfile(profileId); + if (profile == null) { + throw new ProfileNotFoundException(profileId); + } + + profile.getConfigStore().load(new ByteArrayInputStream(data)); + ps.disableProfile(profileId); + profile.getConfigStore().commit(false); + + return createOKResponse(data); + } catch (EBaseException | IOException e) { + CMS.debug("modifyProfile: error modifying profile `" + profileId + "`: " + e); + e.printStackTrace(); + throw new PKIException("Error modifying profile."); + } + } + private void changeProfileData(ProfileData data, IProfile profile) { String profileId = data.getId(); if (profile == null) { diff --git a/base/common/src/com/netscape/certsrv/profile/ProfileClient.java b/base/common/src/com/netscape/certsrv/profile/ProfileClient.java index 51d159aca687719a2dace939da5b09c4809872ec..f3ffd29e03c1da39518e3928eab7a781ec38eaff 100644 --- a/base/common/src/com/netscape/certsrv/profile/ProfileClient.java +++ b/base/common/src/com/netscape/certsrv/profile/ProfileClient.java @@ -45,6 +45,11 @@ public class ProfileClient extends Client { return client.getEntity(response, ProfileData.class); } + public byte[] retrieveProfileRaw(String id) { + Response response = profileClient.retrieveProfileRaw(id); + return client.getEntity(response, byte[].class); + } + public ProfileDataInfos listProfiles(Integer start, Integer size) { Response response = profileClient.listProfiles(start, size); return client.getEntity(response, ProfileDataInfos.class); @@ -65,11 +70,21 @@ public class ProfileClient extends Client { return client.getEntity(response, ProfileData.class); } + public byte[] createProfileRaw(byte[] data) { + Response response = profileClient.createProfileRaw(data); + return client.getEntity(response, byte[].class); + } + public ProfileData modifyProfile(ProfileData data) { Response response = profileClient.modifyProfile(data.getId(), data); return client.getEntity(response, ProfileData.class); } + public byte[] modifyProfileRaw(String profileId, byte[] data) { + Response response = profileClient.modifyProfileRaw(profileId, data); + return client.getEntity(response, byte[].class); + } + public void deleteProfile(String id) { Response response = profileClient.deleteProfile(id); client.getEntity(response, Void.class); diff --git a/base/common/src/com/netscape/certsrv/profile/ProfileResource.java b/base/common/src/com/netscape/certsrv/profile/ProfileResource.java index 87449b27e749c6088f65b53192eb5ac101263f1e..410f98a468dbfca0bf587623935f2a63e97923e9 100644 --- a/base/common/src/com/netscape/certsrv/profile/ProfileResource.java +++ b/base/common/src/com/netscape/certsrv/profile/ProfileResource.java @@ -31,12 +31,24 @@ public interface ProfileResource { @ACLMapping("profiles.read") public Response retrieveProfile(@PathParam("id") String id); + @GET + @Path("{id}/raw") + @ClientResponseType(entityType=byte[].class) + @ACLMapping("profiles.read") + public Response retrieveProfileRaw(@PathParam("id") String id); + @POST @ClientResponseType(entityType=ProfileData.class) @ACLMapping("profiles.create") public Response createProfile(ProfileData data); @POST + @Path("raw") + @ClientResponseType(entityType=byte[].class) + @ACLMapping("profiles.create") + public Response createProfileRaw(byte[] data); + + @POST @Path("{id}") @ClientResponseType(entityType=Void.class) @ACLMapping("profiles.approve") @@ -48,9 +60,15 @@ public interface ProfileResource { @ACLMapping("profiles.modify") public Response modifyProfile(@PathParam("id") String id, ProfileData data); + @PUT + @Path("{id}/raw") + @ClientResponseType(entityType=byte[].class) + @ACLMapping("profiles.modify") + public Response modifyProfileRaw(@PathParam("id") String id, byte[] data); + @DELETE @Path("{id}") @ClientResponseType(entityType=Void.class) @ACLMapping("profiles.delete") public Response deleteProfile(@PathParam("id") String id); -} \ No newline at end of file +} diff --git a/base/java-tools/src/com/netscape/cmstools/profile/ProfileAddCLI.java b/base/java-tools/src/com/netscape/cmstools/profile/ProfileAddCLI.java index 62bd145261a798fd89b3b8aaed389d0b2fb886fa..74690810c20d5b8a2271f46ba2877f15aba6e001 100644 --- a/base/java-tools/src/com/netscape/cmstools/profile/ProfileAddCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/profile/ProfileAddCLI.java @@ -1,11 +1,16 @@ package com.netscape.cmstools.profile; -import java.io.FileNotFoundException; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.Arrays; +import java.util.Properties; import javax.xml.bind.JAXBException; import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; import org.apache.commons.cli.ParseException; import com.netscape.certsrv.profile.ProfileData; @@ -19,6 +24,10 @@ public class ProfileAddCLI extends CLI { public ProfileAddCLI(ProfileCLI profileCLI) { super("add", "Add profiles", profileCLI); this.profileCLI = profileCLI; + + Option optRaw = new Option(null, "raw", false, "Use raw format"); + optRaw.setArgName("raw"); + options.addOption(optRaw); } public void printHelp() { @@ -59,13 +68,29 @@ public class ProfileAddCLI extends CLI { } try { - ProfileData data = ProfileCLI.readProfileFromFile(filename); - data = profileCLI.profileClient.createProfile(data); + if (cmd.hasOption("raw")) { + byte[] data = Files.readAllBytes(Paths.get(filename)); + Properties cs = new Properties(); + cs.load(new ByteArrayInputStream(data)); + String profileId = cs.getProperty("profileId"); + if (profileId == null) { + System.err.println("Error: Missing profileId property in profile data."); + System.exit(-1); + } - MainCLI.printMessage("Added profile " + data.getId()); + byte[] profileConfig = + profileCLI.profileClient.createProfileRaw(data); + System.out.println(new String(profileConfig)); + MainCLI.printMessage("Added profile " + profileId); + } else { + ProfileData data = ProfileCLI.readProfileFromFile(filename); + data = profileCLI.profileClient.createProfile(data); - ProfileCLI.printProfile(data, profileCLI.getClient().getConfig().getServerURI()); - } catch (FileNotFoundException | JAXBException e) { + MainCLI.printMessage("Added profile " + data.getId()); + + ProfileCLI.printProfile(data, profileCLI.getClient().getConfig().getServerURI()); + } + } catch (IOException | JAXBException e) { System.err.println("Error: " + e.getMessage()); System.exit(-1); } diff --git a/base/java-tools/src/com/netscape/cmstools/profile/ProfileCLI.java b/base/java-tools/src/com/netscape/cmstools/profile/ProfileCLI.java index 732b597afd1dbb5b9440d451f34b2f39e20fb904..1b69a4a457efb8af1e58c60f711a4ec7055ad066 100644 --- a/base/java-tools/src/com/netscape/cmstools/profile/ProfileCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/profile/ProfileCLI.java @@ -32,6 +32,7 @@ public class ProfileCLI extends CLI { addModule(new ProfileShowCLI(this)); addModule(new ProfileAddCLI(this)); addModule(new ProfileModifyCLI(this)); + addModule(new ProfileEditCLI(this)); addModule(new ProfileRemoveCLI(this)); addModule(new ProfileEnableCLI(this)); addModule(new ProfileDisableCLI(this)); diff --git a/base/java-tools/src/com/netscape/cmstools/profile/ProfileEditCLI.java b/base/java-tools/src/com/netscape/cmstools/profile/ProfileEditCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..5d8b9a50eb1fd23199b4e893906d5170e1ff791e --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/profile/ProfileEditCLI.java @@ -0,0 +1,102 @@ +//--- BEGIN COPYRIGHT BLOCK --- +//This program is free software; you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation; version 2 of the License. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You should have received a copy of the GNU General Public License along +//with this program; if not, write to the Free Software Foundation, Inc., +//51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +//(C) 2014 Red Hat, Inc. +//All rights reserved. +//--- END COPYRIGHT BLOCK --- + +package com.netscape.cmstools.profile; + +import java.lang.ProcessBuilder; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.ParseException; + +import com.netscape.cmstools.cli.CLI; + +public class ProfileEditCLI extends CLI { + + public ProfileCLI profileCLI; + + public ProfileEditCLI(ProfileCLI profileCLI) { + super("edit", "Edit profiles (config-store format)", profileCLI); + this.profileCLI = profileCLI; + } + + public void printHelp() { + formatter.printHelp(getFullName() + " [OPTIONS...]", options); + } + + public void execute(String[] args) throws Exception { + // Always check for "--help" prior to parsing + if (Arrays.asList(args).contains("--help")) { + // Display usage + printHelp(); + System.exit(0); + } + + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + String[] cmdArgs = cmd.getArgs(); + + if (cmdArgs.length < 1) { + System.err.println("Error: No Profile ID specified."); + printHelp(); + System.exit(-1); + } + + String profileId = cmdArgs[0]; + + // read profile into temporary file + byte[] orig = profileCLI.profileClient.retrieveProfileRaw(profileId); + Path tempFile = Files.createTempFile("pki", ".cfg"); + Files.write(tempFile, orig); + + // invoke editor on temporary file + String editor = System.getenv("EDITOR"); + String[] command; + if (editor == null || editor.trim().isEmpty()) { + command = new String[] {"/usr/bin/env", "vi", tempFile.toString()}; + } else { + command = new String[] {editor.trim(), tempFile.toString()}; + } + ProcessBuilder pb = new ProcessBuilder(command); + pb.inheritIO(); + int exitCode = pb.start().waitFor(); + if (exitCode != 0) { + System.err.println("Error: editor exited abnormally."); + System.exit(-1); + } + + // read data from temporary file and modify if changed + byte[] cur = Files.readAllBytes(tempFile); + Files.delete(tempFile); + String curString = new String(cur); + if (!curString.equals(new String(orig))) { + profileCLI.profileClient.modifyProfileRaw(profileId, cur); + } + System.out.println(curString); + } +} diff --git a/base/java-tools/src/com/netscape/cmstools/profile/ProfileModifyCLI.java b/base/java-tools/src/com/netscape/cmstools/profile/ProfileModifyCLI.java index bbeb91981e79b6690efa55c8f293b017ea4ace31..1cf1991d6a22887ce141f317094bdf9209311a44 100644 --- a/base/java-tools/src/com/netscape/cmstools/profile/ProfileModifyCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/profile/ProfileModifyCLI.java @@ -1,11 +1,16 @@ package com.netscape.cmstools.profile; -import java.io.FileNotFoundException; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.Arrays; +import java.util.Properties; import javax.xml.bind.JAXBException; import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; import org.apache.commons.cli.ParseException; import com.netscape.certsrv.profile.ProfileData; @@ -19,6 +24,10 @@ public class ProfileModifyCLI extends CLI { public ProfileModifyCLI(ProfileCLI profileCLI) { super("mod", "Modify profiles", profileCLI); this.profileCLI = profileCLI; + + Option optRaw = new Option(null, "raw", false, "Use raw format"); + optRaw.setArgName("raw"); + options.addOption(optRaw); } public void printHelp() { @@ -59,14 +68,28 @@ public class ProfileModifyCLI extends CLI { } try { - ProfileData data = ProfileCLI.readProfileFromFile(filename); - data = profileCLI.profileClient.modifyProfile(data); + if (cmd.hasOption("raw")) { + byte[] data = Files.readAllBytes(Paths.get(filename)); + Properties cs = new Properties(); + cs.load(new ByteArrayInputStream(data)); + String profileId = cs.getProperty("profileId"); + if (profileId == null) { + System.err.println("Error: Missing profileId property in profile data."); + System.exit(-1); + } - MainCLI.printMessage("Modified profile " + data.getId()); + byte[] profileConfig = + profileCLI.profileClient.modifyProfileRaw(profileId, data); + System.out.println(new String(profileConfig)); + } else { + ProfileData data = ProfileCLI.readProfileFromFile(filename); + data = profileCLI.profileClient.modifyProfile(data); - ProfileCLI.printProfile(data, profileCLI.getClient().getConfig().getServerURI()); + MainCLI.printMessage("Modified profile " + data.getId()); - } catch (FileNotFoundException | JAXBException e) { + ProfileCLI.printProfile(data, profileCLI.getClient().getConfig().getServerURI()); + } + } catch (IOException | JAXBException e) { System.err.println("Error: " + e.getMessage()); System.exit(-1); } diff --git a/base/java-tools/src/com/netscape/cmstools/profile/ProfileShowCLI.java b/base/java-tools/src/com/netscape/cmstools/profile/ProfileShowCLI.java index f5b636c1a8f9dbfe60d29fc07bae373be3ea966e..7776cbd86a0ede6d2b19a9adb3618dc4652bd8ae 100644 --- a/base/java-tools/src/com/netscape/cmstools/profile/ProfileShowCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/profile/ProfileShowCLI.java @@ -1,5 +1,6 @@ package com.netscape.cmstools.profile; +import java.io.FileOutputStream; import java.util.Arrays; import org.apache.commons.cli.CommandLine; @@ -26,9 +27,13 @@ public class ProfileShowCLI extends CLI { } public void createOptions() { - Option option = new Option(null, "output", true, "Output filename"); - option.setArgName("filename"); - options.addOption(option); + Option optFilename = new Option(null, "output", true, "Output filename"); + optFilename.setArgName("filename"); + options.addOption(optFilename); + + Option optRaw = new Option(null, "raw", false, "Use raw format"); + optRaw.setArgName("raw"); + options.addOption(optRaw); } public void execute(String[] args) throws Exception { @@ -70,14 +75,24 @@ public class ProfileShowCLI extends CLI { } } - ProfileData profileData = profileCLI.profileClient.retrieveProfile(profileId); - MainCLI.printMessage("Profile \"" + profileId + "\""); + if (cmd.hasOption("raw")) { + byte[] profileConfig = profileCLI.profileClient.retrieveProfileRaw(profileId); - if (filename != null) { - ProfileCLI.saveProfileToFile(filename, profileData); + if (filename != null) { + (new FileOutputStream(filename)).write(profileConfig); + MainCLI.printMessage("Saved profile " + profileId + " to " + filename); + } else { + System.out.println(new String(profileConfig)); + } } else { - ProfileCLI.printProfile(profileData, profileCLI.getClient().getConfig().getServerURI()); + ProfileData profileData = profileCLI.profileClient.retrieveProfile(profileId); + + if (filename != null) { + ProfileCLI.saveProfileToFile(filename, profileData); + } else { + ProfileCLI.printProfile(profileData, profileCLI.getClient().getConfig().getServerURI()); + } } } -- 1.9.3