Skip to content

plexus-sec-dispatcher 3.0.0 #73

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Sep 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading