Skip to content

Commit

Permalink
plexus-sec-dispatcher 3.0.0 (#73)
Browse files Browse the repository at this point in the history
This library is now Java 17. Fixed all the "planned" but never done feats.
  • Loading branch information
cstamas authored Sep 28, 2024
1 parent 3c4b0f7 commit e2eb1b6
Show file tree
Hide file tree
Showing 23 changed files with 1,147 additions and 470 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ jobs:
build:
name: Build it
uses: codehaus-plexus/.github/.github/workflows/maven.yml@master
with:
jdk-matrix: '[ "23", "21", "17" ]'

deploy:
name: Deploy
Expand Down
39 changes: 24 additions & 15 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</parent>

<artifactId>plexus-sec-dispatcher</artifactId>
<version>2.1.0-SNAPSHOT</version>
<version>3.0.0-SNAPSHOT</version>

<name>Plexus Security Dispatcher Component</name>

Expand All @@ -33,31 +33,30 @@
</distributionManagement>

<properties>
<javaVersion>17</javaVersion>
<project.build.outputTimestamp>2023-05-22T22:22:22Z</project.build.outputTimestamp>
</properties>

<dependencies>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-xml</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
<version>4.0.2</version>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-cipher</artifactId>
<version>2.0</version>
<version>3.0.0</version>
</dependency>

<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.sisu</groupId>
<artifactId>org.eclipse.sisu.inject</artifactId>
<version>${sisuMavenPluginVersion}</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
Expand All @@ -76,7 +75,7 @@
<artifactId>modello-maven-plugin</artifactId>
<version>2.4.0</version>
<configuration>
<version>1.0.0</version>
<version>3.0.0</version>
<models>
<model>src/main/mdo/settings-security.mdo</model>
</models>
Expand All @@ -86,12 +85,22 @@
<id>standard</id>
<goals>
<goal>java</goal>
<goal>xpp3-reader</goal>
<goal>xpp3-writer</goal>
<goal>xsd</goal>
<goal>stax-reader</goal>
<goal>stax-writer</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<environmentVariables>
<MASTER_PASSWORD>masterPw</MASTER_PASSWORD>
</environmentVariables>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (c) 2008 Sonatype, Inc. All rights reserved.
*
* This program is licensed to you under the Apache License Version 2.0,
* and you may not use this file except in compliance with the Apache License Version 2.0.
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the Apache License Version 2.0 is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/

package org.codehaus.plexus.components.secdispatcher;

import java.util.Map;
import java.util.Set;

/**
* This component decrypts a string, passed to it
*
* @author Oleg Gusakov
*/
public interface SecDispatcher {
/**
* The default path of configuration.
* <p>
* The character {@code ~} (tilde) may be present as first character ONLY and is
* interpreted as "user home".
*/
String DEFAULT_CONFIGURATION = "~/.m2/settings-security.xml";

/**
* Java System Property that may be set, to override configuration path.
*/
String SYSTEM_PROPERTY_CONFIGURATION_LOCATION = "settings.security";

/**
* Attribute that selects a dispatcher.
*
* @see #availableDispatchers()
*/
String DISPATCHER_NAME_ATTR = "name";

/**
* Returns the set of available dispatcher names, never {@code null}.
*/
Set<String> availableDispatchers();

/**
* encrypt given plaintext string
*
* @param str the plaintext to encrypt
* @param attr the attributes, may be {@code null}
* @return encrypted string
* @throws SecDispatcherException in case of problem
*/
String encrypt(String str, Map<String, String> attr) throws SecDispatcherException;

/**
* decrypt given encrypted string
*
* @param str the encrypted string
* @return plaintext string
* @throws SecDispatcherException in case of problem
*/
String decrypt(String str) throws SecDispatcherException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,13 @@
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/

package org.sonatype.plexus.components.sec.dispatcher;
package org.codehaus.plexus.components.secdispatcher;

public class SecDispatcherException extends Exception {
public class SecDispatcherException extends RuntimeException {
public SecDispatcherException(String message) {
super(message);
}

public SecDispatcherException(Throwable cause) {
super(cause);
}

public SecDispatcherException(String message, Throwable cause) {
super(message, cause);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/*
* Copyright (c) 2008 Sonatype, Inc. All rights reserved.
*
* This program is licensed to you under the Apache License Version 2.0,
* and you may not use this file except in compliance with the Apache License Version 2.0.
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the Apache License Version 2.0 is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/

package org.codehaus.plexus.components.secdispatcher.internal;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.stream.Collectors;

import org.codehaus.plexus.components.cipher.PlexusCipher;
import org.codehaus.plexus.components.cipher.PlexusCipherException;
import org.codehaus.plexus.components.secdispatcher.SecDispatcher;
import org.codehaus.plexus.components.secdispatcher.SecDispatcherException;
import org.codehaus.plexus.components.secdispatcher.model.SettingsSecurity;

import static java.util.Objects.requireNonNull;

/**
* @author Oleg Gusakov
*/
@Singleton
@Named
public class DefaultSecDispatcher implements SecDispatcher {
public static final String ATTR_START = "[";
public static final String ATTR_STOP = "]";

protected final PlexusCipher cipher;
protected final Map<String, MasterPasswordSource> masterPasswordSources;
protected final Map<String, Dispatcher> dispatchers;
protected final String configurationFile;

@Inject
public DefaultSecDispatcher(
PlexusCipher cipher,
Map<String, MasterPasswordSource> masterPasswordSources,
Map<String, Dispatcher> dispatchers,
@Named("${configurationFile:-" + DEFAULT_CONFIGURATION + "}") final String configurationFile) {
this.cipher = requireNonNull(cipher);
this.masterPasswordSources = requireNonNull(masterPasswordSources);
this.dispatchers = requireNonNull(dispatchers);
this.configurationFile = requireNonNull(configurationFile);
}

@Override
public Set<String> availableDispatchers() {
return Set.copyOf(dispatchers.keySet());
}

@Override
public String encrypt(String str, Map<String, String> attr) throws SecDispatcherException {
if (isEncryptedString(str)) return str;

try {
String res;
if (attr == null || attr.get(DISPATCHER_NAME_ATTR) == null) {
SettingsSecurity sec = getConfiguration(true);
String master = getMasterPassword(sec, true);
res = cipher.encrypt(getMasterCipher(sec), str, master);
} else {
String type = attr.get(DISPATCHER_NAME_ATTR);
Dispatcher dispatcher = dispatchers.get(type);
if (dispatcher == null) throw new SecDispatcherException("no dispatcher for name " + type);
res = ATTR_START
+ attr.entrySet().stream()
.map(e -> e.getKey() + "=" + e.getValue())
.collect(Collectors.joining(","))
+ ATTR_STOP;
res += dispatcher.encrypt(str, attr, prepareDispatcherConfig(type));
}
return cipher.decorate(res);
} catch (PlexusCipherException e) {
throw new SecDispatcherException(e.getMessage(), e);
}
}

@Override
public String decrypt(String str) throws SecDispatcherException {
if (!isEncryptedString(str)) return str;
try {
String bare = cipher.unDecorate(str);
Map<String, String> attr = stripAttributes(bare);
if (attr == null || attr.get(DISPATCHER_NAME_ATTR) == null) {
SettingsSecurity sec = getConfiguration(true);
String master = getMasterPassword(sec, true);
return cipher.decrypt(getMasterCipher(sec), bare, master);
} else {
String type = attr.get(DISPATCHER_NAME_ATTR);
Dispatcher dispatcher = dispatchers.get(type);
if (dispatcher == null) throw new SecDispatcherException("no dispatcher for name " + type);
return dispatcher.decrypt(strip(bare), attr, prepareDispatcherConfig(type));
}
} catch (PlexusCipherException e) {
throw new SecDispatcherException(e.getMessage(), e);
}
}

private Map<String, String> prepareDispatcherConfig(String type) {
HashMap<String, String> dispatcherConf = new HashMap<>();
SettingsSecurity sec = getConfiguration(false);
String master = getMasterPassword(sec, false);
if (master != null) {
dispatcherConf.put(Dispatcher.CONF_MASTER_PASSWORD, master);
}
Map<String, String> conf = SecUtil.getConfig(sec, type);
if (conf != null) {
dispatcherConf.putAll(conf);
}
return dispatcherConf;
}

private String strip(String str) {
int start = str.indexOf(ATTR_START);
int stop = str.indexOf(ATTR_STOP);
if (start != -1 && stop != -1 && stop > start) {
return str.substring(stop + 1);
}
return str;
}

private Map<String, String> stripAttributes(String str) {
int start = str.indexOf(ATTR_START);
int stop = str.indexOf(ATTR_STOP);
if (start != -1 && stop != -1 && stop > start) {
if (start != 0) throw new SecDispatcherException("Attributes can be prefix only");
if (stop == start + 1) return null;
String attrs = str.substring(start + 1, stop).trim();
if (attrs.isEmpty()) return null;
Map<String, String> res = null;
StringTokenizer st = new StringTokenizer(attrs, ",");
while (st.hasMoreTokens()) {
if (res == null) res = new HashMap<>(st.countTokens());
String pair = st.nextToken();
int pos = pair.indexOf('=');
if (pos == -1) throw new SecDispatcherException("Attribute malformed: " + pair);
String key = pair.substring(0, pos).trim();
String val = pair.substring(pos + 1).trim();
res.put(key, val);
}
return res;
}
return null;
}

private boolean isEncryptedString(String str) {
if (str == null) return false;
return cipher.isEncryptedString(str);
}

private SettingsSecurity getConfiguration(boolean mandatory) throws SecDispatcherException {
String location = System.getProperty(SYSTEM_PROPERTY_CONFIGURATION_LOCATION, getConfigurationFile());
location = location.charAt(0) == '~' ? System.getProperty("user.home") + location.substring(1) : location;
SettingsSecurity sec = SecUtil.read(location, true);
if (mandatory && sec == null)
throw new SecDispatcherException("Please check that configuration file on path " + location + " exists");

return sec;
}

private String getMasterPassword(SettingsSecurity sec, boolean mandatory) throws SecDispatcherException {
if (sec == null && !mandatory) {
return null;
}
requireNonNull(sec, "configuration is null");
String masterSource = requireNonNull(sec.getMasterSource(), "masterSource is null");
for (MasterPasswordSource masterPasswordSource : masterPasswordSources.values()) {
String masterPassword = masterPasswordSource.handle(masterSource);
if (masterPassword != null) return masterPassword;
}
if (mandatory) {
throw new SecDispatcherException("master password could not be fetched");
} else {
return null;
}
}

private String getMasterCipher(SettingsSecurity sec) throws SecDispatcherException {
requireNonNull(sec, "configuration is null");
return requireNonNull(sec.getMasterCipher(), "masterCipher is null");
}

public String getConfigurationFile() {
return configurationFile;
}
}
Loading

0 comments on commit e2eb1b6

Please sign in to comment.