Skip to content

Commit

Permalink
4.x: Unified logic, no more extra logic in SecDispatcher; made self-d…
Browse files Browse the repository at this point in the history
…escribable (#75)

Hence, moved version to 4.x.

Changes:
* "master pw" is **really just one (of possibly multiple) dispatchers**, so moved "master password" into dispatchers. Now SecDispatcher is really just a "dispatcher" with one `Dispatcher` implementation OOTB
* similarly, old sec behaviour is moved to dispatcher as "legacy" to support mvn3 encrypted passwords (only decrypting)
* dispatcher passwords now minimally include "name" attribute (and more, if dispatcher adds it), master dispatcher adds "cipher" as well, this allows future proof working if new Cipher added and user makes it default: non-reencoded passwords will still work with old cipher.
* `Dispatcher` and `MasterSource` interfaces have corresponding "meta" interfaces where they can "describe themselves"
* added support for pinentry as well
* configuration validation now performs "deep validation" and produces useful report
* this is no more OOTB ready-to-use JSR330 component. As we know from history, it WAS before, and it provided a "bad" default configuration path that was NEVER used, and forced Maven instead to _always redefine_ the component to make it usable within Maven (to make it use maven expected config). So, it is now the integrator duty to "properly integrate" this component, recommended way is to create JSR330 Provider with it. Also, the "system property override" of configuration path was removed, whatever integrating app needs is doable from it's own Provider implementation.

Self describe and validation in action:
https://asciinema.org/a/9kmtQWhKJC9elFp3tiDlxpOTE
  • Loading branch information
cstamas authored Oct 14, 2024
1 parent 75f4891 commit ab0942e
Show file tree
Hide file tree
Showing 28 changed files with 1,752 additions and 514 deletions.
20 changes: 18 additions & 2 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>3.0.1-SNAPSHOT</version>
<version>4.0.0-SNAPSHOT</version>

<name>Plexus Security Dispatcher Component</name>

Expand All @@ -35,9 +35,16 @@
<properties>
<javaVersion>17</javaVersion>
<project.build.outputTimestamp>2024-09-29T15:16:00Z</project.build.outputTimestamp>

<version.slf4j>2.0.16</version.slf4j>
</properties>

<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${version.slf4j}</version>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-cipher</artifactId>
Expand All @@ -62,6 +69,12 @@
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>${version.slf4j}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand All @@ -75,7 +88,7 @@
<artifactId>modello-maven-plugin</artifactId>
<version>2.4.0</version>
<configuration>
<version>3.0.0</version>
<version>4.0.0</version>
<models>
<model>src/main/mdo/settings-security.mdo</model>
</models>
Expand All @@ -96,6 +109,9 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<masterPassword>masterPw</masterPassword>
</systemPropertyVariables>
<environmentVariables>
<MASTER_PASSWORD>masterPw</MASTER_PASSWORD>
</environmentVariables>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/

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

import java.util.Map;

import org.codehaus.plexus.components.secdispatcher.SecDispatcherException;
import static java.util.Objects.requireNonNull;

/**
* Dispatcher.
Expand All @@ -26,31 +26,51 @@
*/
public interface Dispatcher {
/**
* Configuration key for masterPassword. It may be present, if SecDispatcher could
* obtain it, but presence is optional. Still, dispatcher may throw and fail the operation
* if it requires it.
* The "encrypt payload" prepared by dispatcher.
*/
String CONF_MASTER_PASSWORD = "masterPassword";
final class EncryptPayload {
private final Map<String, String> attributes;
private final String encrypted;

public EncryptPayload(Map<String, String> attributes, String encrypted) {
this.attributes = requireNonNull(attributes);
this.encrypted = requireNonNull(encrypted);
}

public Map<String, String> getAttributes() {
return attributes;
}

public String getEncrypted() {
return encrypted;
}
}

/**
* encrypt given plaintext string
* Encrypt given plaintext string. Implementation must return at least same attributes it got, but may add more
* attributes to returned payload.
*
* @param str string to encrypt
* @param str string to encrypt, never {@code null}
* @param attributes attributes, never {@code null}
* @param config configuration from settings-security.xml, never {@code null}
* @return encrypted string
* @return encrypted string and attributes in {@link EncryptPayload}
*/
String encrypt(String str, Map<String, String> attributes, Map<String, String> config)
EncryptPayload encrypt(String str, Map<String, String> attributes, Map<String, String> config)
throws SecDispatcherException;

/**
* decrypt given encrypted string
* Decrypt given encrypted string.
*
* @param str string to decrypt
* @param str string to decrypt, never {@code null}
* @param attributes attributes, never {@code null}
* @param config configuration from settings-security.xml, never {@code null}
* @return decrypted string
*/
String decrypt(String str, Map<String, String> attributes, Map<String, String> config)
throws SecDispatcherException;

/**
* Validates dispatcher configuration.
*/
SecDispatcher.ValidationResponse validateConfiguration(Map<String, String> config);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package org.codehaus.plexus.components.secdispatcher;

import java.util.Collection;
import java.util.List;
import java.util.Optional;

import static java.util.Objects.requireNonNull;

/**
* Meta description of dispatcher.
*/
public interface DispatcherMeta {
final class Field {
private final String key;
private final boolean optional;
private final String defaultValue;
private final String description;
private final List<Field> options;

private Field(String key, boolean optional, String defaultValue, String description, List<Field> options) {
this.key = requireNonNull(key);
this.optional = optional;
this.defaultValue = defaultValue;
this.description = requireNonNull(description);
this.options = options;
}

/**
* The key to be used in configuration map for field.
*/
public String getKey() {
return key;
}

/**
* Is configuration optional?
*/
public boolean isOptional() {
return optional;
}

/**
* Optional default value of the configuration.
*/
public Optional<String> getDefaultValue() {
return Optional.ofNullable(defaultValue);
}

/**
* The human description of the configuration.
*/
public String getDescription() {
return description;
}

/**
* Optional list of options, if this configuration accepts limited values. Each option is represented
* as field, where {@link #getKey()} represents the value to be used, and {@link #displayName()} represents
* the description of option. The {@link #getDefaultValue()}, if present represents the value to be used
* instead of {@link #getKey()}.
*/
public Optional<List<Field>> getOptions() {
return Optional.ofNullable(options);
}

public static Builder builder(String key) {
return new Builder(key);
}

public static final class Builder {
private final String key;
private boolean optional;
private String defaultValue;
private String description;
private List<Field> options;

private Builder(String key) {
this.key = requireNonNull(key);
}

public Builder optional(boolean optional) {
this.optional = optional;
return this;
}

public Builder defaultValue(String defaultValue) {
this.defaultValue = defaultValue;
return this;
}

public Builder description(String description) {
this.description = requireNonNull(description);
return this;
}

public Builder options(List<Field> options) {
this.options = requireNonNull(options);
return this;
}

public Field build() {
return new Field(key, optional, defaultValue, description, options);
}
}
}

/**
* Option to hide this instance from users, like for migration or legacy purposes.
*/
default boolean isHidden() {
return false;
}

/**
* The name of the dispatcher.
*/
String name();

/**
* Returns the display (human) name of the dispatcher.
*/
String displayName();

/**
* Returns the configuration fields of the dispatcher.
*/
Collection<Field> fields();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* 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;

/**
* Source of master password.
*/
public interface MasterSource {
/**
* Handles the config to get master password. Implementation may do one of the following things:
* <ul>
* <li>if the config cannot be handled by given source, return {@code null}</li>
* <li>otherwise, if master password retrieval based on config was attempted but failed, throw {@link SecDispatcherException}</li>
* <li>happy path: return the master password.</li>
* </ul>
*
* @param config the source of master password, and opaque string.
* @return the master password, or {@code null} if implementation does not handle this config
* @throws SecDispatcherException If implementation does handle this masterSource, but cannot obtain master password
*/
String handle(String config) throws SecDispatcherException;

/**
* Validates master source configuration.
* <ul>
* <li>if the config cannot be handled by given source, return {@code null}</li>
* <li>otherwise, implementation performs validation and returns non-{@code null} validation response</li>
* </ul>
*/
SecDispatcher.ValidationResponse validateConfiguration(String config);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,22 @@
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/

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

import org.codehaus.plexus.components.secdispatcher.SecDispatcherException;
import org.codehaus.plexus.components.secdispatcher.internal.MasterPasswordSource;
import java.util.Optional;

import static java.util.Objects.requireNonNull;

public class StaticMasterPasswordSource implements MasterPasswordSource {
private final String masterPassword;

public StaticMasterPasswordSource(String masterPassword) {
this.masterPassword = requireNonNull(masterPassword);
}
/**
* Source of master password.
*/
public interface MasterSourceMeta {
/**
* String describing what this source does.
*/
String description();

@Override
public String handle(String masterSource) throws SecDispatcherException {
return masterPassword;
}
/**
* Optional "config template" that may serve as basis to configure this master source. The template cannot be
* "reused" as is as configuration.
*/
Optional<String> configTemplate();
}
Loading

0 comments on commit ab0942e

Please sign in to comment.