mirror of
https://github.com/octoleo/plantuml.git
synced 2024-12-22 10:59:01 +00:00
Merge pull request #796 from arittner/master
Authentication for SURL with BasicAuth and OAuth2
This commit is contained in:
commit
0668801c0e
@ -84,7 +84,7 @@ public class FileSystem {
|
||||
|
||||
}
|
||||
}
|
||||
for (SFile d : SecurityUtils.getPath("plantuml.include.path")) {
|
||||
for (SFile d : SecurityUtils.getPath(SecurityUtils.PATHS_INCLUDES)) {
|
||||
assert d.isDirectory();
|
||||
final SFile file = d.file(nameOrPath);
|
||||
if (file.exists()) {
|
||||
@ -92,7 +92,7 @@ public class FileSystem {
|
||||
|
||||
}
|
||||
}
|
||||
for (SFile d : SecurityUtils.getPath("java.class.path")) {
|
||||
for (SFile d : SecurityUtils.getPath(SecurityUtils.PATHS_CLASSES)) {
|
||||
assert d.isDirectory();
|
||||
final SFile file = d.file(nameOrPath);
|
||||
if (file.exists()) {
|
||||
|
@ -108,12 +108,12 @@ public class ImportedFiles {
|
||||
public List<SFile> getPath() {
|
||||
final List<SFile> result = new ArrayList<>(imported);
|
||||
result.addAll(includePath());
|
||||
result.addAll(SecurityUtils.getPath("java.class.path"));
|
||||
result.addAll(SecurityUtils.getPath(SecurityUtils.PATHS_CLASSES));
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<SFile> includePath() {
|
||||
return SecurityUtils.getPath("plantuml.include.path");
|
||||
return SecurityUtils.getPath(SecurityUtils.PATHS_INCLUDES);
|
||||
}
|
||||
|
||||
private boolean isAbsolute(String nameOrPath) {
|
||||
|
@ -35,6 +35,7 @@
|
||||
*/
|
||||
package net.sourceforge.plantuml.security;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
@ -51,13 +52,13 @@ import java.io.PrintWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
/**
|
||||
* Secure replacement for java.io.File.
|
||||
* <p>
|
||||
@ -246,11 +247,19 @@ public class SFile implements Comparable<SFile> {
|
||||
// In SANDBOX, we cannot read any files
|
||||
return false;
|
||||
}
|
||||
// In any case SFile should not access the security folders (the files must be handled internally)
|
||||
try {
|
||||
if (isDenied()) {
|
||||
return false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
// Files in "plantuml.include.path" and "plantuml.allowlist.path" are ok.
|
||||
if (isInAllowList(SecurityUtils.getPath("plantuml.include.path"))) {
|
||||
if (isInAllowList(SecurityUtils.getPath(SecurityUtils.PATHS_INCLUDES))) {
|
||||
return true;
|
||||
}
|
||||
if (isInAllowList(SecurityUtils.getPath("plantuml.allowlist.path"))) {
|
||||
if (isInAllowList(SecurityUtils.getPath(SecurityUtils.PATHS_ALLOWED))) {
|
||||
return true;
|
||||
}
|
||||
if (SecurityUtils.getSecurityProfile() == SecurityProfile.INTERNET) {
|
||||
@ -284,6 +293,35 @@ public class SFile implements Comparable<SFile> {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks, if the SFile is inside the folder (-structure) of the security area.
|
||||
*
|
||||
* @return true, if the file is not allowed to read/write
|
||||
* @throws IOException If an I/O error occurs, which is possible because the check the pathname may require
|
||||
* filesystem queries
|
||||
*/
|
||||
private boolean isDenied() throws IOException {
|
||||
SFile securityPath = SecurityUtils.getSecurityPath();
|
||||
if (securityPath == null) {
|
||||
return false;
|
||||
}
|
||||
return getSanitizedPath().startsWith(securityPath.getSanitizedPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sanitized, canonical and normalized Path to a file.
|
||||
*
|
||||
* @return the Path
|
||||
* @throws IOException If an I/O error occurs, which is possible because the construction of the canonical pathname
|
||||
* may require filesystem queries
|
||||
* @see #getCleanPathSecure()
|
||||
* @see File#getCanonicalPath()
|
||||
* @see Path#normalize()
|
||||
*/
|
||||
private Path getSanitizedPath() throws IOException {
|
||||
return Paths.get(new File(getCleanPathSecure()).getCanonicalPath()).normalize();
|
||||
}
|
||||
|
||||
private String getCleanPathSecure() {
|
||||
String result = internal.getAbsolutePath();
|
||||
result = result.replace("\0", "");
|
||||
|
@ -40,9 +40,14 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -54,29 +59,71 @@ import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
import net.sourceforge.plantuml.StringUtils;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityAccessInterceptor;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityAuthentication;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityCredentials;
|
||||
|
||||
/**
|
||||
* Secure replacement for java.net.URL.
|
||||
* <p>
|
||||
* This class should be used instead of java.net.URL.
|
||||
* <p>
|
||||
* This class does some control access.
|
||||
*
|
||||
* This class does some control access and manages access-tokens via URL. If a URL contains a access-token, similar to
|
||||
* a user prefix, SURL loads the authorization config for this user-token and passes the credentials to the host.
|
||||
* <p>
|
||||
* Example:<br/>
|
||||
* <pre>
|
||||
* SURL url = SURL.create ("https://jenkins-access@jenkins.mycompany.com/api/json")
|
||||
* </pre>
|
||||
* The {@code jenkins-access} will checked against the Security context access token configuration. If a configuration
|
||||
* exists for this token name, the token will be removed from the URL and the credentials will be added to the headers.
|
||||
* If the token is not found, the URL remains as it is and no separate authentication will be performed.
|
||||
* <p>
|
||||
* TODO: Some methods should be moved to a HttpClient implementation, because SURL is not the valid class to manage it.
|
||||
* <br/>
|
||||
* TODO: BAD_HOSTS implementation should be reviewed and moved to HttpClient implementation with a circuit-breaker.
|
||||
* <br/>
|
||||
* TODO: Token expiration with refresh should be implemented in future.
|
||||
* <br/>
|
||||
*/
|
||||
public class SURL {
|
||||
|
||||
/**
|
||||
* Indicates, that we have no authentication to access the URL.
|
||||
*/
|
||||
public static final String WITHOUT_AUTHENTICATION = SecurityUtils.NO_CREDENTIALS;
|
||||
|
||||
/**
|
||||
* Regex to remove the UserInfo part from a URL.
|
||||
*/
|
||||
private static final Pattern PATTERN_USERINFO = Pattern.compile("(^https?://)(.*@)(.*)");
|
||||
|
||||
private static final ExecutorService EXE = Executors.newCachedThreadPool();
|
||||
|
||||
private static final Map<String, Long> BAD_HOSTS = new ConcurrentHashMap<String, Long>();
|
||||
|
||||
/**
|
||||
* Internal URL, maybe cleaned from user-token.
|
||||
*/
|
||||
private final URL internal;
|
||||
|
||||
private SURL(String src) throws MalformedURLException {
|
||||
this(new URL(src));
|
||||
}
|
||||
/**
|
||||
* Assigned credentials to this URL.
|
||||
*/
|
||||
private final String securityIdentifier;
|
||||
|
||||
private SURL(URL url) {
|
||||
private SURL(URL url, String securityIdentifier) {
|
||||
assert url != null;
|
||||
assert securityIdentifier != null;
|
||||
this.internal = url;
|
||||
this.securityIdentifier = securityIdentifier;
|
||||
}
|
||||
|
||||
public static SURL create(String url) {
|
||||
@ -85,7 +132,7 @@ public class SURL {
|
||||
}
|
||||
if (url.startsWith("http://") || url.startsWith("https://"))
|
||||
try {
|
||||
return new SURL(url);
|
||||
return create(new URL(url));
|
||||
} catch (MalformedURLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@ -96,7 +143,47 @@ public class SURL {
|
||||
if (url == null) {
|
||||
return null;
|
||||
}
|
||||
return new SURL(url);
|
||||
|
||||
URL internalUrl;
|
||||
|
||||
String credentialId = url.getUserInfo();
|
||||
|
||||
if (credentialId == null || credentialId.indexOf(':') > 0) {
|
||||
// No user info at all, or a user with password (This is a legacy BasicAuth access, and we bypass it):
|
||||
internalUrl = url;
|
||||
credentialId = WITHOUT_AUTHENTICATION;
|
||||
} else {
|
||||
// Given userInfo, but without a password. We try to find SecurityCredentials
|
||||
if (SecurityUtils.existsSecurityCredentials(credentialId) ) {
|
||||
internalUrl = removeUserInfo (url);
|
||||
} else {
|
||||
internalUrl = url;
|
||||
credentialId = WITHOUT_AUTHENTICATION;
|
||||
}
|
||||
}
|
||||
|
||||
return new SURL(internalUrl, credentialId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a URL without UserInfo part and without SecurityCredentials.
|
||||
*
|
||||
* @param url plain URL
|
||||
* @return SURL without any user credential information.
|
||||
*/
|
||||
public static SURL createWithoutUser(URL url) {
|
||||
return new SURL(removeUserInfo(url), WITHOUT_AUTHENTICATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the bad hosts cache.
|
||||
* <p>
|
||||
* In some test cases (and maybe also needed for other functionality) the bad hosts cache must be cleared.<br/>
|
||||
* E.g., in a test we check the failure on missing credentials and then a test with existing credentials. With a
|
||||
* bad host cache the second test will fail, or we have unpredicted results.
|
||||
*/
|
||||
public static void resetBadHosts() {
|
||||
BAD_HOSTS.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -105,7 +192,7 @@ public class SURL {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check SecurityProfile to see if this URL can be open.
|
||||
* Check SecurityProfile to see if this URL can be opened.
|
||||
*/
|
||||
private boolean isUrlOk() {
|
||||
if (SecurityUtils.getSecurityProfile() == SecurityProfile.SANDBOX) {
|
||||
@ -128,18 +215,13 @@ public class SURL {
|
||||
}
|
||||
final int port = internal.getPort();
|
||||
// Using INTERNET profile, port 80 and 443 are ok
|
||||
if (port == 80 || port == 443 || port == -1) {
|
||||
return true;
|
||||
}
|
||||
return port == 80 || port == 443 || port == -1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean pureIP(String full) {
|
||||
if (full.matches("^https?://\\d+\\.\\d+\\.\\d+\\.\\d+.*")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return full.matches("^https?://\\d+\\.\\d+\\.\\d+\\.\\d+.*");
|
||||
}
|
||||
|
||||
private boolean isInAllowList() {
|
||||
@ -153,6 +235,8 @@ public class SURL {
|
||||
}
|
||||
|
||||
private String cleanPath(String path) {
|
||||
// Remove user information, because we don't like to store user/password or userTokens in allow-list
|
||||
path = removeUserInfoFromUrlPath (path);
|
||||
path = path.trim().toLowerCase(Locale.US);
|
||||
// We simplify/normalize the url, removing default ports
|
||||
path = path.replace(":80/", "");
|
||||
@ -161,72 +245,268 @@ public class SURL {
|
||||
}
|
||||
|
||||
private List<String> getAllowList() {
|
||||
final String env = SecurityUtils.getenv("plantuml.allowlist.url");
|
||||
final String env = SecurityUtils.getenv(SecurityUtils.PATHS_ALLOWED);
|
||||
if (env == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Arrays.asList(StringUtils.eventuallyRemoveStartingAndEndingDoubleQuote(env).split(";"));
|
||||
}
|
||||
|
||||
private final static ExecutorService exe = Executors.newCachedThreadPool();
|
||||
private final static Map<String, Long> badHosts = new ConcurrentHashMap<String, Long>();
|
||||
|
||||
// Added by Alain Corbiere
|
||||
/**
|
||||
* Reads from an endpoint (with configured credentials and proxy) the response as blob.
|
||||
* <p>
|
||||
* This method allows access to an endpoint, with a configured SecurityCredentials object. The credentials will
|
||||
* load on the fly and authentication fetched from an authentication-manager. Caching of tokens is not supported.
|
||||
* <p>
|
||||
* authors: Alain Corbiere, Aljoscha Rittner
|
||||
*
|
||||
* @return data loaded data from endpoint
|
||||
*/
|
||||
public byte[] getBytes() {
|
||||
if (isUrlOk() == false) {
|
||||
if (!isUrlOk()) {
|
||||
return null;
|
||||
}
|
||||
final String host = internal.getHost();
|
||||
final Long bad = badHosts.get(host);
|
||||
if (bad != null) {
|
||||
final long duration = System.currentTimeMillis() - bad;
|
||||
if (duration < 1000L * 60) {
|
||||
// System.err.println("BAD HOST!" + host);
|
||||
return null;
|
||||
}
|
||||
// System.err.println("cleaning " + host);
|
||||
badHosts.remove(host);
|
||||
}
|
||||
final Future<byte[]> result = exe.submit(new Callable<byte[]>() {
|
||||
public byte[] call() throws IOException {
|
||||
InputStream input = null;
|
||||
try {
|
||||
final URLConnection connection = internal.openConnection();
|
||||
if (connection == null) {
|
||||
return null;
|
||||
}
|
||||
input = connection.getInputStream();
|
||||
final ByteArrayOutputStream image = new ByteArrayOutputStream();
|
||||
final byte[] buffer = new byte[1024];
|
||||
int read;
|
||||
while ((read = input.read(buffer)) > 0) {
|
||||
image.write(buffer, 0, read);
|
||||
}
|
||||
image.close();
|
||||
return image.toByteArray();
|
||||
} finally {
|
||||
if (input != null) {
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
SecurityCredentials credentials = SecurityUtils.loadSecurityCredentials(securityIdentifier);
|
||||
SecurityAuthentication authentication = SecurityUtils.getAuthenticationManager(credentials).create(credentials);
|
||||
try {
|
||||
byte data[] = result.get(SecurityUtils.getSecurityProfile().getTimeout(), TimeUnit.MILLISECONDS);
|
||||
if (data != null) {
|
||||
return data;
|
||||
String host = internal.getHost();
|
||||
Long bad = BAD_HOSTS.get(host);
|
||||
if (bad != null) {
|
||||
if ((System.currentTimeMillis() - bad) < 1000L * 60) {
|
||||
return null;
|
||||
}
|
||||
BAD_HOSTS.remove(host);
|
||||
}
|
||||
|
||||
try {
|
||||
Future<byte[]> result = EXE.submit(
|
||||
requestWithGetAndResponse(internal, credentials.getProxy(), authentication, null));
|
||||
byte[] data = result.get(SecurityUtils.getSecurityProfile().getTimeout(), TimeUnit.MILLISECONDS);
|
||||
if (data != null) {
|
||||
return data;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("issue " + host + " " + e);
|
||||
}
|
||||
|
||||
BAD_HOSTS.put(host, System.currentTimeMillis());
|
||||
return null;
|
||||
} finally {
|
||||
// clean up. We don't cache tokens, no expire handling. All time a re-request.
|
||||
credentials.eraseCredentials();
|
||||
authentication.eraseCredentials();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads from an endpoint with a given authentication and proxy the response as blob.
|
||||
* <p>
|
||||
* This method allows a parametrized access to an endpoint, without a configured SecurityCredentials object. This is
|
||||
* useful to access internally identity providers (IDP), or authorization servers (to request access tokens).
|
||||
* <p>
|
||||
* This method don't use the "bad-host" functionality, because the access to infrastructure services should not be
|
||||
* obfuscated by some internal management.
|
||||
* <p>
|
||||
* <strong>Please don't use this method directly from DSL scripts.</strong>
|
||||
*
|
||||
* @param authentication authentication object data. Caller is responsible to erase credentials
|
||||
* @param proxy proxy configuration
|
||||
* @param headers additional headers, if needed
|
||||
* @return loaded data from endpoint
|
||||
*/
|
||||
public byte[] getBytes (Proxy proxy, SecurityAuthentication authentication, Map<String, Object> headers) {
|
||||
if (!isUrlOk()) {
|
||||
return null;
|
||||
}
|
||||
final Future<byte[]> result = EXE.submit(requestWithGetAndResponse(internal, proxy, authentication, headers));
|
||||
byte[] data = null;
|
||||
try {
|
||||
data = result.get(SecurityUtils.getSecurityProfile().getTimeout(), TimeUnit.MILLISECONDS);
|
||||
} catch (Exception e) {
|
||||
System.err.println("SURL response issue to " + internal.getHost() + " " + e);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Post to an endpoint with a given authentication and proxy the response as blob.
|
||||
* <p>
|
||||
* This method allows a parametrized access to an endpoint, without a configured SecurityCredentials object. This is
|
||||
* useful to access internally identity providers (IDP), or authorization servers (to request access tokens).
|
||||
* <p>
|
||||
* This method don't use the "bad-host" functionality, because the access to infrastructure services should not be
|
||||
* obfuscated by some internal management.
|
||||
* <p>
|
||||
* <strong>Please don't use this method directly from DSL scripts.</strong>
|
||||
*
|
||||
* @param authentication authentication object data. Caller is responsible to erase credentials
|
||||
* @param proxy proxy configuration
|
||||
* @param data content to post
|
||||
* @param headers headers, if needed
|
||||
* @return loaded data from endpoint
|
||||
*/
|
||||
public byte[] getBytesOnPost(Proxy proxy, SecurityAuthentication authentication, String data,
|
||||
Map<String, Object> headers) {
|
||||
if (!isUrlOk()) {
|
||||
return null;
|
||||
}
|
||||
final Future<byte[]> result = EXE.submit(
|
||||
requestWithPostAndResponse(internal, proxy, authentication, data, headers));
|
||||
byte[] response = null;
|
||||
try {
|
||||
response = result.get(SecurityUtils.getSecurityProfile().getTimeout(), TimeUnit.MILLISECONDS);
|
||||
} catch (Exception e) {
|
||||
System.err.println("SURL response issue to " + internal.getHost() + " " + e);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a GET request and response handler
|
||||
* @param url URL to request
|
||||
* @param proxy proxy to apply
|
||||
* @param authentication the authentication to use
|
||||
* @param headers additional headers, if needed
|
||||
* @return the callable handler.
|
||||
*/
|
||||
private static Callable<byte[]> requestWithGetAndResponse(
|
||||
final URL url, final Proxy proxy, final SecurityAuthentication authentication,
|
||||
final Map<String, Object> headers) {
|
||||
return new Callable<byte[]>() {
|
||||
public byte[] call() throws IOException {
|
||||
// Add proxy, if passed throw parameters
|
||||
final URLConnection connection = proxy == null
|
||||
? url.openConnection()
|
||||
: url.openConnection(proxy);
|
||||
if (connection == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
HttpURLConnection http = (HttpURLConnection) connection;
|
||||
|
||||
applyEndpointAccessAuthentication(http, authentication);
|
||||
applyAdditionalHeaders (http, headers);
|
||||
|
||||
return retrieveResponseAsBytes(http);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a POST request and response handler with a simple String content. The content will be identified as form
|
||||
* or JSON data. The charset encoding can be set by header parameters or will be set to UTF-8. The method to some
|
||||
* fancy logic to simplify it for the user.
|
||||
* @param url URL to request via POST method
|
||||
* @param proxy proxy to apply
|
||||
* @param authentication the authentication to use
|
||||
* @param headers additional headers, if needed
|
||||
* @return the callable handler.
|
||||
*/
|
||||
private static Callable<byte[]> requestWithPostAndResponse(
|
||||
final URL url, final Proxy proxy, final SecurityAuthentication authentication,
|
||||
final String data, final Map<String, Object> headers) {
|
||||
return new Callable<byte[]>() {
|
||||
public byte[] call() throws IOException {
|
||||
// Add proxy, if passed throw parameters
|
||||
final URLConnection connection = proxy == null
|
||||
? url.openConnection()
|
||||
: url.openConnection(proxy);
|
||||
if (connection == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean withContent = StringUtils.isNotEmpty(data);
|
||||
|
||||
HttpURLConnection http = (HttpURLConnection) connection;
|
||||
http.setRequestMethod("POST");
|
||||
if (withContent) {
|
||||
http.setDoOutput(true);
|
||||
}
|
||||
|
||||
applyEndpointAccessAuthentication(http, authentication);
|
||||
applyAdditionalHeaders (http, headers);
|
||||
|
||||
Charset charSet = extractCharset(http.getRequestProperty("Content-Type"));
|
||||
|
||||
if ( withContent ) {
|
||||
sendRequestAsBytes(http, data.getBytes(charSet != null ? charSet : StandardCharsets.UTF_8));
|
||||
}
|
||||
return retrieveResponseAsBytes(http);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static Charset extractCharset(String contentType) {
|
||||
if ( StringUtils.isEmpty(contentType) ) {
|
||||
return null;
|
||||
}
|
||||
Matcher matcher = Pattern.compile("(?i)\\bcharset=\\s*\"?([^\\s;\"]*)").matcher(contentType);
|
||||
if (matcher.find()) {
|
||||
try {
|
||||
return Charset.forName(matcher.group(1));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("issue " + host + " " + e);
|
||||
}
|
||||
badHosts.put(host, System.currentTimeMillis());
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a response from an endpoint as a byte[] array.
|
||||
*
|
||||
* @param connection the URL connection
|
||||
* @return the loaded byte arrays
|
||||
* @throws IOException an exception, if the connection cannot establish or the download was broken
|
||||
*/
|
||||
private static byte[] retrieveResponseAsBytes(HttpURLConnection connection) throws IOException {
|
||||
int responseCode = connection.getResponseCode();
|
||||
if ( responseCode < HttpURLConnection.HTTP_BAD_REQUEST) {
|
||||
try (InputStream input = connection.getInputStream()) {
|
||||
return retrieveData(input);
|
||||
}
|
||||
} else {
|
||||
try (InputStream error = connection.getErrorStream()) {
|
||||
byte[] bytes = retrieveData(error);
|
||||
throw new IOException("HTTP error "
|
||||
+ responseCode + " with " + new String(bytes, StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads data in a byte[] array.
|
||||
* @param input input stream
|
||||
* @return byte data
|
||||
* @throws IOException if something went wrong
|
||||
*/
|
||||
private static byte[] retrieveData (InputStream input) throws IOException {
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
final byte[] buffer = new byte[1024];
|
||||
int read;
|
||||
while ((read = input.read(buffer)) > 0) {
|
||||
out.write(buffer, 0, read);
|
||||
}
|
||||
out.close();
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request content payload to an endpoint.
|
||||
* @param connection HTTP connection
|
||||
* @param data data as byte array
|
||||
* @throws IOException if something went wrong
|
||||
*/
|
||||
private static void sendRequestAsBytes (HttpURLConnection connection, byte[] data) throws IOException {
|
||||
connection.setFixedLengthStreamingMode(data.length);
|
||||
try (OutputStream os = connection.getOutputStream()) {
|
||||
os.write(data);
|
||||
}
|
||||
}
|
||||
|
||||
public InputStream openStream() {
|
||||
if (isUrlOk()) {
|
||||
final byte data[] = getBytes();
|
||||
final byte[] data = getBytes();
|
||||
if (data != null) {
|
||||
return new ByteArrayInputStream(data);
|
||||
}
|
||||
@ -245,4 +525,86 @@ public class SURL {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Informs, if SecurityCredentials are configured for this connection.
|
||||
*
|
||||
* @return true, if credentials will be used for a connection
|
||||
*/
|
||||
public boolean isAuthorizationConfigured() {
|
||||
return !WITHOUT_AUTHENTICATION.equals(securityIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the given authentication data to the http connection.
|
||||
*
|
||||
* @param http HTTP URL connection (must be an encrypted https-TLS/SSL connection, or http must be
|
||||
* activated with a property)
|
||||
* @param authentication the data to request the access
|
||||
* @see SecurityUtils#getAccessInterceptor(SecurityAuthentication)
|
||||
* @see SecurityUtils#isNonSSLAuthenticationAllowed()
|
||||
*/
|
||||
private static void applyEndpointAccessAuthentication (URLConnection http, SecurityAuthentication authentication) {
|
||||
if (authentication.isPublic()) {
|
||||
// Shortcut: No need to apply authentication.
|
||||
return;
|
||||
}
|
||||
if (http instanceof HttpsURLConnection || SecurityUtils.isNonSSLAuthenticationAllowed()) {
|
||||
SecurityAccessInterceptor accessInterceptor = SecurityUtils.getAccessInterceptor(authentication);
|
||||
accessInterceptor.apply(authentication, http);
|
||||
} else {
|
||||
// We cannot allow applying secret tokens on plain connections. Everyone can read the data.
|
||||
throw new IllegalStateException(
|
||||
"The transport of authentication data over an unencrypted http connection is not allowed");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the headers for a URL connection
|
||||
* @param headers map Keys with values (can be String or list of String)
|
||||
*/
|
||||
private static void applyAdditionalHeaders(URLConnection http, Map<String, Object> headers) {
|
||||
if ( headers == null || headers.isEmpty() ) {
|
||||
return;
|
||||
}
|
||||
for (Map.Entry<String, Object> header : headers.entrySet()) {
|
||||
Object value = header.getValue();
|
||||
if ( value instanceof String ) {
|
||||
http.setRequestProperty(header.getKey(), (String) value);
|
||||
} else if (value instanceof List) {
|
||||
for ( Object item: (List<?>) value) {
|
||||
if ( item != null ) {
|
||||
http.addRequestProperty(header.getKey(), item.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the userInfo part from the URL, because we want to use the SecurityCredentials instead.
|
||||
* @param url URL with UserInfo part
|
||||
* @return url without UserInfo part
|
||||
*/
|
||||
private static URL removeUserInfo(URL url) {
|
||||
try {
|
||||
return new URL(removeUserInfoFromUrlPath (url.toExternalForm()));
|
||||
} catch (MalformedURLException e) {
|
||||
e.printStackTrace();
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the userInfo part from the URL, because we want to use the SecurityCredentials instead.
|
||||
* @param url URL with UserInfo part
|
||||
* @return url without UserInfo part
|
||||
*/
|
||||
private static String removeUserInfoFromUrlPath(String url) {
|
||||
// Simple solution:
|
||||
Matcher matcher = PATTERN_USERINFO.matcher(url);
|
||||
if (matcher.find()) {
|
||||
return matcher.replaceFirst("$1$3");
|
||||
}
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
@ -5,12 +5,12 @@
|
||||
* (C) Copyright 2009-2020, Arnaud Roques
|
||||
*
|
||||
* Project Info: http://plantuml.com
|
||||
*
|
||||
*
|
||||
* If you like this project or if you find it useful, you can support us at:
|
||||
*
|
||||
*
|
||||
* http://plantuml.com/patreon (only 1$ per month!)
|
||||
* http://plantuml.com/paypal
|
||||
*
|
||||
*
|
||||
* This file is part of PlantUML.
|
||||
*
|
||||
* PlantUML is free software; you can redistribute it and/or modify it
|
||||
@ -30,13 +30,34 @@
|
||||
*
|
||||
*
|
||||
* Original Author: Arnaud Roques
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
package net.sourceforge.plantuml.security;
|
||||
|
||||
import net.sourceforge.plantuml.OptionFlags;
|
||||
import net.sourceforge.plantuml.StringUtils;
|
||||
import net.sourceforge.plantuml.json.Json;
|
||||
import net.sourceforge.plantuml.json.JsonValue;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityAccessInterceptor;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityAuthentication;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityAuthorizeManager;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityCredentials;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityDefaultNoopAccessInterceptor;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityDefaultNoopAuthorizeManager;
|
||||
import net.sourceforge.plantuml.security.authentication.basicauth.BasicAuthAccessInterceptor;
|
||||
import net.sourceforge.plantuml.security.authentication.basicauth.BasicAuthAuthorizeManager;
|
||||
import net.sourceforge.plantuml.security.authentication.oauth.OAuth2AccessInterceptor;
|
||||
import net.sourceforge.plantuml.security.authentication.oauth.OAuth2ClientAccessAuthorizeManager;
|
||||
import net.sourceforge.plantuml.security.authentication.oauth.OAuth2ResourceOwnerAccessAuthorizeManager;
|
||||
import net.sourceforge.plantuml.security.authentication.token.TokenAuthAccessInterceptor;
|
||||
import net.sourceforge.plantuml.security.authentication.token.TokenAuthAuthorizeManager;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
@ -44,20 +65,100 @@ import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
import net.sourceforge.plantuml.OptionFlags;
|
||||
import net.sourceforge.plantuml.StringUtils;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class SecurityUtils {
|
||||
|
||||
/**
|
||||
* Indicates, that we have no authentication and credentials to access the URL.
|
||||
*/
|
||||
public static final String NO_CREDENTIALS = "<none>";
|
||||
|
||||
/**
|
||||
* Java class paths to import files from.
|
||||
*/
|
||||
public static final String PATHS_CLASSES = "java.class.path";
|
||||
|
||||
/**
|
||||
* Paths to include files.
|
||||
*/
|
||||
public static final String PATHS_INCLUDES = "plantuml.include.path";
|
||||
|
||||
/**
|
||||
* Whitelist of paths from where scripts can load data.
|
||||
*/
|
||||
public static final String PATHS_ALLOWED = "plantuml.allowlist.path";
|
||||
|
||||
/**
|
||||
* Paths to folders with security specific content (not allowed to read via SFile).
|
||||
*/
|
||||
public static final String PATHS_SECURITY = "plantuml.security.credentials.path";
|
||||
|
||||
public static final String SECURITY_ALLOW_NONSSL_AUTH = "plantuml.security.allowNonSSLAuth";
|
||||
|
||||
/**
|
||||
* Standard BasicAuth authentication interceptor, to generate a SecurityAuthentication from credentials.
|
||||
*/
|
||||
private static final SecurityAuthorizeManager PUBLIC_AUTH_MANAGER
|
||||
= new SecurityDefaultNoopAuthorizeManager();
|
||||
|
||||
/**
|
||||
* Standard interceptor for public endpoint access.
|
||||
*/
|
||||
private static final SecurityAccessInterceptor PUBLIC_ACCESS_INTERCEPTOR
|
||||
= new SecurityDefaultNoopAccessInterceptor();
|
||||
|
||||
/**
|
||||
* Standard TokenAuth authorize manager, to generate a SecurityAuthentication from credentials.
|
||||
*/
|
||||
private static final SecurityAuthorizeManager TOKEN_AUTH_MANAGER
|
||||
= new TokenAuthAuthorizeManager();
|
||||
|
||||
/**
|
||||
* Standard token access interceptor.
|
||||
*/
|
||||
private static final SecurityAccessInterceptor TOKEN_ACCESS_INTERCEPTOR = new TokenAuthAccessInterceptor();
|
||||
|
||||
/**
|
||||
* Standard BasicAuth authorize manager, to generate a SecurityAuthentication from credentials.
|
||||
*/
|
||||
private static final SecurityAuthorizeManager BASICAUTH_AUTH_MANAGER
|
||||
= new BasicAuthAuthorizeManager();
|
||||
|
||||
/**
|
||||
* Standard BasicAuth access interceptor.
|
||||
*/
|
||||
private static final SecurityAccessInterceptor BASICAUTH_ACCESS_INTERCEPTOR = new BasicAuthAccessInterceptor();
|
||||
|
||||
/**
|
||||
* OAuth2 client credentials authorization manager.
|
||||
*/
|
||||
private static final SecurityAuthorizeManager OAUTH2_CLIENT_AUTH_MANAGER
|
||||
= new OAuth2ClientAccessAuthorizeManager();
|
||||
|
||||
/**
|
||||
* OAuth2 resource owner authorization manager.
|
||||
*/
|
||||
private static final SecurityAuthorizeManager OAUTH2_RESOURCEOWNER_AUTH_MANAGER
|
||||
= new OAuth2ResourceOwnerAccessAuthorizeManager();
|
||||
|
||||
/**
|
||||
* Standard 'bearer' OAuth2 access interceptor.
|
||||
*/
|
||||
private static final SecurityAccessInterceptor OAUTH2_ACCESS_INTERCEPTOR = new OAuth2AccessInterceptor();
|
||||
|
||||
/**
|
||||
* Filesystem-save characters.
|
||||
*/
|
||||
private static final Pattern SECURE_CHARS = Pattern.compile("^[a-zA-Z0-9\\-]+$");
|
||||
|
||||
static private SecurityProfile current = null;
|
||||
|
||||
public static synchronized SecurityProfile getSecurityProfile() {
|
||||
@ -83,6 +184,27 @@ public class SecurityUtils {
|
||||
return System.getenv(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the environment variable and returns true if the variable is used in security context. In this case, the
|
||||
* value should not be displayed in scripts.
|
||||
*
|
||||
* @param name Environment variable to check
|
||||
* @return true, if this is a secret variable
|
||||
*/
|
||||
public static boolean isSecurityEnv(String name) {
|
||||
return name != null && name.toLowerCase().startsWith("plantuml.security.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for Non-SSL authentication methods.
|
||||
*
|
||||
* @return true, if plantUML should allow authentication in plain connections (without encryption).
|
||||
* @see #SECURITY_ALLOW_NONSSL_AUTH
|
||||
*/
|
||||
public static boolean isNonSSLAuthenticationAllowed() {
|
||||
return Boolean.parseBoolean(getenv(SECURITY_ALLOW_NONSSL_AUTH));
|
||||
}
|
||||
|
||||
public static List<SFile> getPath(String prop) {
|
||||
final List<SFile> result = new ArrayList<>();
|
||||
String paths = getenv(prop);
|
||||
@ -155,4 +277,144 @@ public class SecurityUtils {
|
||||
return new FileOutputStream(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the authorize-manager for a security credentials configuration.
|
||||
*
|
||||
* @param credentialConfiguration the credentials
|
||||
* @return the manager.
|
||||
*/
|
||||
public static SecurityAuthorizeManager getAuthenticationManager(SecurityCredentials credentialConfiguration) {
|
||||
if (credentialConfiguration == SecurityCredentials.NONE) {
|
||||
return PUBLIC_AUTH_MANAGER;
|
||||
} else if ("tokenauth".equalsIgnoreCase(credentialConfiguration.getType())) {
|
||||
return TOKEN_AUTH_MANAGER;
|
||||
} else if ("basicauth".equalsIgnoreCase(credentialConfiguration.getType())) {
|
||||
return BASICAUTH_AUTH_MANAGER;
|
||||
} else if ("oauth2".equalsIgnoreCase(credentialConfiguration.getType())) {
|
||||
String grantType = credentialConfiguration.getPropertyStr("grantType");
|
||||
if ("client_credentials".equalsIgnoreCase(grantType)) {
|
||||
return OAUTH2_CLIENT_AUTH_MANAGER;
|
||||
} else if ("password".equalsIgnoreCase(grantType)) {
|
||||
return OAUTH2_RESOURCEOWNER_AUTH_MANAGER;
|
||||
}
|
||||
}
|
||||
return PUBLIC_AUTH_MANAGER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the authentication interceptor for a {@link SecurityAuthentication}.
|
||||
*
|
||||
* @param authentication the authentication data
|
||||
* @return the interceptor.
|
||||
*/
|
||||
public static SecurityAccessInterceptor getAccessInterceptor(SecurityAuthentication authentication) {
|
||||
if (authentication != null) {
|
||||
String type = authentication.getType();
|
||||
if ("public".equals(type)) {
|
||||
return PUBLIC_ACCESS_INTERCEPTOR;
|
||||
} else if ("tokenauth".equalsIgnoreCase(type)) {
|
||||
return TOKEN_ACCESS_INTERCEPTOR;
|
||||
} else if ("basicauth".equalsIgnoreCase(type)) {
|
||||
return BASICAUTH_ACCESS_INTERCEPTOR;
|
||||
} else if ("oauth2".equalsIgnoreCase(type)) {
|
||||
return OAUTH2_ACCESS_INTERCEPTOR;
|
||||
}
|
||||
}
|
||||
// Unknown? Fall back to public:
|
||||
return PUBLIC_ACCESS_INTERCEPTOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if user credentials existing.
|
||||
*
|
||||
* @param userToken name of the credential file
|
||||
* @return boolean, if exists
|
||||
*/
|
||||
public static boolean existsSecurityCredentials(String userToken) {
|
||||
SFile securityPath = getSecurityPath();
|
||||
if (securityPath != null) {
|
||||
// SFile does not allow access to the security path (to hide the credentials in DSL scripts)
|
||||
File securityFilePath = securityPath.conv();
|
||||
File userCredentials = new File(securityFilePath, userToken + ".credential");
|
||||
return userCredentials.exists() && userCredentials.canRead() && !userCredentials.isDirectory()
|
||||
&& userCredentials.length() > 2;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the user credentials from the file system.
|
||||
*
|
||||
* @param userToken name of the credential file
|
||||
* @return the credentials or NONE
|
||||
*/
|
||||
public static SecurityCredentials loadSecurityCredentials(String userToken) {
|
||||
if (userToken != null && checkFileSystemSaveCharactersStrict(userToken) && !NO_CREDENTIALS.equals(userToken)) {
|
||||
SFile securityPath = getSecurityPath();
|
||||
if (securityPath != null) {
|
||||
// SFile does not allow access to the security path (to hide the credentials in DSL scripts)
|
||||
File securityFilePath = securityPath.conv();
|
||||
File userCredentials = new File(securityFilePath, userToken + ".credential");
|
||||
JsonValue jsonValue = loadJson(userCredentials);
|
||||
return SecurityCredentials.fromJson(jsonValue);
|
||||
}
|
||||
}
|
||||
return SecurityCredentials.NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks, if the token of a pathname (filename, ext, directory-name) uses only a very strict set of characters and
|
||||
* not longer than 64 characters.
|
||||
* <p>
|
||||
* Only characters from a to Z, Numbers and - are allowed.
|
||||
*
|
||||
* @param pathNameToken filename, ext, directory-name
|
||||
* @return true, if the string fits to the strict allow-list of characters
|
||||
* @see #SECURE_CHARS
|
||||
*/
|
||||
private static boolean checkFileSystemSaveCharactersStrict(String pathNameToken) {
|
||||
return StringUtils.isNotEmpty(pathNameToken)
|
||||
&& SECURE_CHARS.matcher(pathNameToken).matches() && pathNameToken.length() <= 64;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the path to the configured security folder, if existing.
|
||||
* <p>
|
||||
* Please note: A SFile referenced to a security folder cannot access the files. The content of the files in the
|
||||
* security path should never have passed to DSL scripts.
|
||||
*
|
||||
* @return SFile folder or null
|
||||
*/
|
||||
public static SFile getSecurityPath() {
|
||||
List<SFile> paths = getPath(PATHS_SECURITY);
|
||||
if (!paths.isEmpty()) {
|
||||
SFile secureSFile = paths.get(0);
|
||||
File securityFolder = secureSFile.conv();
|
||||
if (securityFolder.exists() && securityFolder.isDirectory()) {
|
||||
return secureSFile;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads a file as JSON object. If no file exists or the file is not parsable, the method returns an empty JSON.
|
||||
*
|
||||
* @param jsonFile file path to the JSON file
|
||||
* @return a Json vale (maybe empty)
|
||||
*/
|
||||
private static JsonValue loadJson(File jsonFile) {
|
||||
if (jsonFile.exists() && jsonFile.canRead() && jsonFile.length() > 2) {
|
||||
// we have a file with at least two bytes and readable, hopefully it's a JSON
|
||||
try (Reader r = new BufferedReader(new FileReader(jsonFile))) {
|
||||
return Json.parse(r);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return Json.object();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,54 @@
|
||||
/* ========================================================================
|
||||
* PlantUML : a free UML diagram generator
|
||||
* ========================================================================
|
||||
*
|
||||
* (C) Copyright 2009-2021, Arnaud Roques
|
||||
*
|
||||
* Project Info: http://plantuml.com
|
||||
*
|
||||
* If you like this project or if you find it useful, you can support us at:
|
||||
*
|
||||
* http://plantuml.com/patreon (only 1$ per month!)
|
||||
* http://plantuml.com/paypal
|
||||
*
|
||||
* This file is part of PlantUML.
|
||||
*
|
||||
* PlantUML 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, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* PlantUML 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 library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
||||
* USA.
|
||||
*
|
||||
*
|
||||
* Original Author: Arnaud Roques
|
||||
*
|
||||
*
|
||||
*/
|
||||
package net.sourceforge.plantuml.security.authentication;
|
||||
|
||||
import java.net.URLConnection;
|
||||
|
||||
/**
|
||||
* The security access interceptor applies the authentication information to a HTTP connection. This can be a
|
||||
* user/password combination for BasicAuth or a bearer token for OAuth2.
|
||||
*
|
||||
* @author Aljoscha Rittner
|
||||
*/
|
||||
public interface SecurityAccessInterceptor {
|
||||
/**
|
||||
* Applies to a connection the authentication information.
|
||||
*
|
||||
* @param authentication the determined authentication data to authorize for the endpoint access
|
||||
* @param connection the connection to the endpoint
|
||||
*/
|
||||
void apply(SecurityAuthentication authentication, URLConnection connection);
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
/* ========================================================================
|
||||
* PlantUML : a free UML diagram generator
|
||||
* ========================================================================
|
||||
*
|
||||
* (C) Copyright 2009-2021, Arnaud Roques
|
||||
*
|
||||
* Project Info: http://plantuml.com
|
||||
*
|
||||
* If you like this project or if you find it useful, you can support us at:
|
||||
*
|
||||
* http://plantuml.com/patreon (only 1$ per month!)
|
||||
* http://plantuml.com/paypal
|
||||
*
|
||||
* This file is part of PlantUML.
|
||||
*
|
||||
* PlantUML 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, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* PlantUML 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 library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
||||
* USA.
|
||||
*
|
||||
*
|
||||
* Original Author: Arnaud Roques
|
||||
*
|
||||
*
|
||||
*/
|
||||
package net.sourceforge.plantuml.security.authentication;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The authentication to access an endpoint. This information will be generated by a SecurityAuthenticationInterceptor.
|
||||
*
|
||||
* @author Aljoscha Rittner
|
||||
*/
|
||||
public class SecurityAuthentication implements SecurityCredentialsContainer {
|
||||
|
||||
/**
|
||||
* Type of authentication (e.g. basicauth, oauth2)
|
||||
*/
|
||||
private final String type;
|
||||
|
||||
/**
|
||||
* Characteristic of an authentication (e.g. openId). Can be null.<p>
|
||||
* This kind of information is typically not needed. Useful for debugging purpose.
|
||||
*/
|
||||
private final String shape;
|
||||
|
||||
/**
|
||||
* Origin authorization process (e.g. client_credentials.<p>
|
||||
* This kind of information is typically not needed. Useful for debugging purpose.
|
||||
*/
|
||||
private final String grantType;
|
||||
|
||||
/**
|
||||
* A map of needed data tokens to authenticate access to an endpoint.
|
||||
*/
|
||||
private final Map<String, Object> tokens;
|
||||
|
||||
public SecurityAuthentication(String type, Map<String, Object> tokens) {
|
||||
this(type, null, null, tokens);
|
||||
}
|
||||
|
||||
public SecurityAuthentication(String type, String shape, String grantType, Map<String, Object> tokens) {
|
||||
this.type = type;
|
||||
this.shape = shape;
|
||||
this.grantType = grantType;
|
||||
this.tokens = tokens;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getShape() {
|
||||
return shape;
|
||||
}
|
||||
|
||||
public String getGrantType() {
|
||||
return grantType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the state of this authentication.
|
||||
*
|
||||
* @return true, if we have no authentication.
|
||||
*/
|
||||
public boolean isPublic() {
|
||||
return "public".equalsIgnoreCase(type) && (tokens == null || tokens.isEmpty());
|
||||
}
|
||||
|
||||
public Map<String, Object> getTokens() {
|
||||
return tokens;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eraseCredentials() {
|
||||
if (tokens != null && !tokens.isEmpty()) {
|
||||
for (Object tokenVal : tokens.values()) {
|
||||
if (tokenVal instanceof char[]) {
|
||||
Arrays.fill((char[]) tokenVal, '*');
|
||||
}
|
||||
}
|
||||
tokens.clear();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/* ========================================================================
|
||||
* PlantUML : a free UML diagram generator
|
||||
* ========================================================================
|
||||
*
|
||||
* (C) Copyright 2009-2021, Arnaud Roques
|
||||
*
|
||||
* Project Info: http://plantuml.com
|
||||
*
|
||||
* If you like this project or if you find it useful, you can support us at:
|
||||
*
|
||||
* http://plantuml.com/patreon (only 1$ per month!)
|
||||
* http://plantuml.com/paypal
|
||||
*
|
||||
* This file is part of PlantUML.
|
||||
*
|
||||
* PlantUML 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, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* PlantUML 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 library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
||||
* USA.
|
||||
*
|
||||
*
|
||||
* Original Author: Arnaud Roques
|
||||
*
|
||||
*
|
||||
*/
|
||||
package net.sourceforge.plantuml.security.authentication;
|
||||
|
||||
/**
|
||||
* Creates from credentials a {@link SecurityAuthentication} object or authorize as principal to retrieve an
|
||||
* authentication object.
|
||||
*
|
||||
* @author Aljoscha Rittner
|
||||
*/
|
||||
public interface SecurityAuthorizeManager {
|
||||
/**
|
||||
* Creates from the credentials the authentication object to access an endpoint. If the credentials defines a
|
||||
* principal (e.g. in OAuth2), the create method should authorize the principal and get the final authentication
|
||||
* data to access an endpoint.
|
||||
*
|
||||
* @param credentials the configured credentials
|
||||
* @return the authentication object.
|
||||
*/
|
||||
SecurityAuthentication create(SecurityCredentials credentials);
|
||||
}
|
@ -0,0 +1,410 @@
|
||||
/* ========================================================================
|
||||
* PlantUML : a free UML diagram generator
|
||||
* ========================================================================
|
||||
*
|
||||
* (C) Copyright 2009-2021, Arnaud Roques
|
||||
*
|
||||
* Project Info: http://plantuml.com
|
||||
*
|
||||
* If you like this project or if you find it useful, you can support us at:
|
||||
*
|
||||
* http://plantuml.com/patreon (only 1$ per month!)
|
||||
* http://plantuml.com/paypal
|
||||
*
|
||||
* This file is part of PlantUML.
|
||||
*
|
||||
* PlantUML 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, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* PlantUML 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 library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
||||
* USA.
|
||||
*
|
||||
*
|
||||
* Original Author: Arnaud Roques
|
||||
*
|
||||
*
|
||||
*/
|
||||
package net.sourceforge.plantuml.security.authentication;
|
||||
|
||||
import net.sourceforge.plantuml.StringUtils;
|
||||
import net.sourceforge.plantuml.json.JsonObject;
|
||||
import net.sourceforge.plantuml.json.JsonValue;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Defines a configuration for credentials.
|
||||
*
|
||||
* @author Aljoscha Rittner
|
||||
*/
|
||||
public class SecurityCredentials implements SecurityCredentialsContainer {
|
||||
|
||||
private static final Map<String, Object> EMPTY_MAP = Collections.emptyMap();
|
||||
|
||||
/**
|
||||
* No credentials given.
|
||||
*/
|
||||
public static final SecurityCredentials NONE = new SecurityCredentials("<NONE>", "public", null, null);
|
||||
|
||||
/**
|
||||
* Name of the configuration.
|
||||
*/
|
||||
private final String name;
|
||||
/**
|
||||
* The type of authorization and access process (e.g. "basicauth" or "oauth2").
|
||||
*/
|
||||
private final String type;
|
||||
/**
|
||||
* Username or client identifier.
|
||||
*/
|
||||
private final String identifier;
|
||||
/**
|
||||
* User/Client secret information.
|
||||
*/
|
||||
private final char[] secret;
|
||||
/**
|
||||
* Properties defined for a specific authorization and access process.
|
||||
*/
|
||||
private final Map<String, Object> properties = new HashMap<String, Object>();
|
||||
/**
|
||||
* Proxy configuration.<p>
|
||||
* <p>
|
||||
* {@link Proxy#NO_PROXY} means, we want direct access. null means, we use the system proxy configuration.
|
||||
*/
|
||||
private final Proxy proxy;
|
||||
|
||||
/**
|
||||
* Creates BasicAuth credentials without a proxy.
|
||||
*
|
||||
* @param name Name of the credentials
|
||||
* @param type The type of authentication and access process (e.g. "basicauth" or "oauth2")
|
||||
* @param identifier username, clientId, ...
|
||||
* @param secret the secret information to authenticate the client or user
|
||||
*/
|
||||
public SecurityCredentials(String name, String type, String identifier, char[] secret) {
|
||||
this(name, type, identifier, secret, EMPTY_MAP, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates BasicAuth credentials with a proxy.
|
||||
*
|
||||
* @param name Name of the credentials
|
||||
* @param type The type of authentication and access process (e.g. "basicauth" or "oauth2")
|
||||
* @param identifier username, clientId, ...
|
||||
* @param secret the secret information to authenticate the client or user
|
||||
* @param proxy proxy configuration
|
||||
*/
|
||||
public SecurityCredentials(String name, String type, String identifier, char[] secret,
|
||||
Map<String, Object> properties, Proxy proxy) {
|
||||
if (name == null) {
|
||||
throw new NullPointerException("Credential name should not be null");
|
||||
}
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.identifier = identifier;
|
||||
this.secret = secret;
|
||||
this.proxy = proxy;
|
||||
this.properties.putAll(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates BasicAuth credentials.
|
||||
*
|
||||
* @param identifier the basic auth user name.
|
||||
* @param secret password
|
||||
* @return credential object
|
||||
*/
|
||||
public static SecurityCredentials basicAuth(String identifier, char[] secret) {
|
||||
return new SecurityCredentials(identifier, "basicauth", identifier, secret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a SecurityCredentials from a JSON.
|
||||
* <p>
|
||||
* Example:
|
||||
* <pre>
|
||||
* {
|
||||
* "name": "jenkins",
|
||||
* "identifier": "alice",
|
||||
* "secret": "secret",
|
||||
* "proxy": {
|
||||
* "type": "socket",
|
||||
* "address": "192.168.1.250",
|
||||
* "port": 8080
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param jsonValue a JSON structure
|
||||
* @return the created SecurityCredentials
|
||||
*/
|
||||
public static SecurityCredentials fromJson(JsonValue jsonValue) {
|
||||
try {
|
||||
JsonObject securityObject = jsonValue.asObject();
|
||||
JsonValue name = securityObject.get("name");
|
||||
JsonValue type = securityObject.get("type");
|
||||
JsonValue identifier = securityObject.get("identifier");
|
||||
JsonValue secret = securityObject.get("secret");
|
||||
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
buildProperties("", securityObject.get("properties"), map);
|
||||
|
||||
if (type != null && !type.isNull() && "tokenauth".equals(type.asString())) {
|
||||
return new SecurityCredentials(name.asString(), "tokenauth",
|
||||
null, null, map, proxyFromJson(securityObject.get("proxy")));
|
||||
} else if (StringUtils.isNotEmpty(name.asString()) && StringUtils.isNotEmpty(identifier.asString())) {
|
||||
String authType = type != null && !type.isNull() ? type.asString() : "basicauth";
|
||||
return new SecurityCredentials(name.asString(), authType,
|
||||
identifier.asString(), extractSecret(secret),
|
||||
map, proxyFromJson(securityObject.get("proxy")));
|
||||
}
|
||||
|
||||
} catch (UnsupportedOperationException use) {
|
||||
// We catch UnsupportedOperationException to stop parsing on unexpected elements
|
||||
}
|
||||
return NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Proxy object from a JSON value.
|
||||
* <p>
|
||||
* Example:
|
||||
* <pre>
|
||||
* {
|
||||
* "type": "socket",
|
||||
* "address": "192.168.1.250",
|
||||
* "port": 8080
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param proxyValue JSON, that represents a Proxy object
|
||||
* @return Proxy object or null
|
||||
*/
|
||||
private static Proxy proxyFromJson(JsonValue proxyValue) {
|
||||
if (proxyValue != null && !proxyValue.isNull() && proxyValue.isObject()) {
|
||||
Proxy.Type type = Proxy.Type.DIRECT;
|
||||
|
||||
JsonObject proxyObject = proxyValue.asObject();
|
||||
JsonValue proxyType = proxyObject.get("type");
|
||||
if (proxyType != null && !proxyType.isNull()) {
|
||||
type = Proxy.Type.valueOf(proxyType.asString().toUpperCase());
|
||||
}
|
||||
if (type == Proxy.Type.DIRECT) {
|
||||
return Proxy.NO_PROXY;
|
||||
}
|
||||
JsonValue proxyAddress = proxyObject.get("address");
|
||||
JsonValue proxyPort = proxyObject.get("port");
|
||||
if (proxyAddress != null && !proxyAddress.isNull() && !proxyPort.isNull() && proxyPort.isNumber()) {
|
||||
InetSocketAddress address = new InetSocketAddress(proxyAddress.asString(), proxyPort.asInt());
|
||||
return new Proxy(type, address);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a password, if it is not empty or null.
|
||||
*
|
||||
* @param pwd password json value
|
||||
* @return password or null
|
||||
*/
|
||||
private static char[] extractSecret(JsonValue pwd) {
|
||||
if (pwd == null || pwd.isNull()) {
|
||||
return null;
|
||||
}
|
||||
String pwdStr = pwd.asString();
|
||||
if (StringUtils.isEmpty(pwdStr)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return pwdStr.toCharArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a properties map from all given key/values.
|
||||
* <p>
|
||||
* Example:<br/>
|
||||
* <pre>
|
||||
* {
|
||||
* "grantType": "client_credentials",
|
||||
* "scope": "read write",
|
||||
* "accessTokenUri": "https://login-demo.curity.io/oauth/v2/oauth-token"
|
||||
* "credentials": {
|
||||
* "identifier": "serviceId",
|
||||
* "secret": "ServiceSecret"
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* will be transformed to:
|
||||
*
|
||||
* grantType -> client_credentials
|
||||
* scope -> read write
|
||||
* accessTokenUri -> https://login-demo.curity.io/oauth/v2/oauth-token
|
||||
* credentials.identifier -> serviceId
|
||||
* credentials.secret -> ServiceSecret
|
||||
* </pre>
|
||||
*
|
||||
* @param prefix the prefix for the direct children
|
||||
* @param fromValue parent JSON value to read from
|
||||
* @param toMap map to populate
|
||||
*/
|
||||
private static void buildProperties(String prefix, JsonValue fromValue, Map<String, Object> toMap) {
|
||||
if (!isJsonObjectWithMembers(fromValue)) {
|
||||
return;
|
||||
}
|
||||
JsonObject members = fromValue.asObject();
|
||||
for (String name : members.names()) {
|
||||
JsonValue child = members.get(name);
|
||||
if (child.isArray() || child.isNull()) {
|
||||
// currently, not supported or not needed
|
||||
continue;
|
||||
}
|
||||
String key = StringUtils.isEmpty(prefix) ? name : prefix + '.' + name;
|
||||
if (child.isObject()) {
|
||||
buildProperties(key, child, toMap);
|
||||
} else {
|
||||
if (child.isString()) {
|
||||
toMap.put(key, child.asString());
|
||||
} else if (child.isBoolean()) {
|
||||
toMap.put(key, child.asBoolean());
|
||||
} else if (child.isNumber()) {
|
||||
toMap.put(key, child.asDouble());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks, if we have a JSON object with members.
|
||||
*
|
||||
* @param jsonValue the value to check
|
||||
* @return true, if we have members in the JSON object
|
||||
*/
|
||||
private static boolean isJsonObjectWithMembers(JsonValue jsonValue) {
|
||||
return jsonValue != null && !jsonValue.isNull() && jsonValue.isObject() && !jsonValue.asObject().isEmpty();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public char[] getSecret() {
|
||||
return secret;
|
||||
}
|
||||
|
||||
public Map<String, Object> getProperties() {
|
||||
return Collections.unmodifiableMap(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the property as String.
|
||||
*
|
||||
* @param key Name of the property
|
||||
* @return String representation
|
||||
*/
|
||||
public String getPropertyStr(String key) {
|
||||
Object value = getProperties().get(key);
|
||||
if (value != null) {
|
||||
return value.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the property as characters.
|
||||
*
|
||||
* @param key Name of the property
|
||||
* @return char[] representation
|
||||
*/
|
||||
public char[] getPropertyChars(String key) {
|
||||
Object value = getProperties().get(key);
|
||||
if (value != null) {
|
||||
return value.toString().toCharArray();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the property as boolean.
|
||||
*
|
||||
* @param key Name of the property
|
||||
* @return boolean representation
|
||||
*/
|
||||
public boolean getPropertyBool(String key) {
|
||||
Object value = getProperties().get(key);
|
||||
if (value != null) {
|
||||
if (value instanceof Boolean) {
|
||||
return (Boolean) value;
|
||||
} else if (value instanceof String) {
|
||||
return Boolean.parseBoolean((String) value);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the property as Number.
|
||||
*
|
||||
* @param key Name of the property
|
||||
* @return boolean representation
|
||||
*/
|
||||
public Number getPropertyNum(String key) {
|
||||
Object value = getProperties().get(key);
|
||||
if (value != null) {
|
||||
if (value instanceof Number) {
|
||||
return (Number) value;
|
||||
} else if (value instanceof String) {
|
||||
return Double.parseDouble((String) value);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Proxy getProxy() {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eraseCredentials() {
|
||||
if (secret != null && secret.length > 0) {
|
||||
Arrays.fill(secret, '*');
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof SecurityCredentials)) return false;
|
||||
SecurityCredentials that = (SecurityCredentials) o;
|
||||
return getName().equals(that.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getName());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/* ========================================================================
|
||||
* PlantUML : a free UML diagram generator
|
||||
* ========================================================================
|
||||
*
|
||||
* (C) Copyright 2009-2021, Arnaud Roques
|
||||
*
|
||||
* Project Info: http://plantuml.com
|
||||
*
|
||||
* If you like this project or if you find it useful, you can support us at:
|
||||
*
|
||||
* http://plantuml.com/patreon (only 1$ per month!)
|
||||
* http://plantuml.com/paypal
|
||||
*
|
||||
* This file is part of PlantUML.
|
||||
*
|
||||
* PlantUML 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, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* PlantUML 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 library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
||||
* USA.
|
||||
*
|
||||
*
|
||||
* Original Author: Arnaud Roques
|
||||
*
|
||||
*
|
||||
*/
|
||||
package net.sourceforge.plantuml.security.authentication;
|
||||
|
||||
/**
|
||||
* Indicates that the implementing object contains sensitive data, which can be erased
|
||||
* using the {@code eraseCredentials} method.
|
||||
*
|
||||
* @author Aljoscha Rittner
|
||||
*/
|
||||
public interface SecurityCredentialsContainer {
|
||||
/**
|
||||
* Get called, if the secret information should be erased.
|
||||
*/
|
||||
void eraseCredentials();
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/* ========================================================================
|
||||
* PlantUML : a free UML diagram generator
|
||||
* ========================================================================
|
||||
*
|
||||
* (C) Copyright 2009-2021, Arnaud Roques
|
||||
*
|
||||
* Project Info: http://plantuml.com
|
||||
*
|
||||
* If you like this project or if you find it useful, you can support us at:
|
||||
*
|
||||
* http://plantuml.com/patreon (only 1$ per month!)
|
||||
* http://plantuml.com/paypal
|
||||
*
|
||||
* This file is part of PlantUML.
|
||||
*
|
||||
* PlantUML 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, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* PlantUML 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 library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
||||
* USA.
|
||||
*
|
||||
*
|
||||
* Original Author: Arnaud Roques
|
||||
*
|
||||
*
|
||||
*/
|
||||
package net.sourceforge.plantuml.security.authentication;
|
||||
|
||||
import java.net.URLConnection;
|
||||
|
||||
/**
|
||||
* This interceptor does nothing.
|
||||
*
|
||||
* @author Aljoscha Rittner
|
||||
*/
|
||||
public class SecurityDefaultNoopAccessInterceptor implements SecurityAccessInterceptor {
|
||||
@Override
|
||||
public void apply(SecurityAuthentication authentication, URLConnection connection) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/* ========================================================================
|
||||
* PlantUML : a free UML diagram generator
|
||||
* ========================================================================
|
||||
*
|
||||
* (C) Copyright 2009-2021, Arnaud Roques
|
||||
*
|
||||
* Project Info: http://plantuml.com
|
||||
*
|
||||
* If you like this project or if you find it useful, you can support us at:
|
||||
*
|
||||
* http://plantuml.com/patreon (only 1$ per month!)
|
||||
* http://plantuml.com/paypal
|
||||
*
|
||||
* This file is part of PlantUML.
|
||||
*
|
||||
* PlantUML 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, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* PlantUML 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 library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
||||
* USA.
|
||||
*
|
||||
*
|
||||
* Original Author: Arnaud Roques
|
||||
*
|
||||
*
|
||||
*/
|
||||
package net.sourceforge.plantuml.security.authentication;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Creates a public access authentication data object.
|
||||
*
|
||||
* @author Aljoscha Rittner
|
||||
*/
|
||||
public class SecurityDefaultNoopAuthorizeManager implements SecurityAuthorizeManager {
|
||||
|
||||
private static final Map<String, Object> EMPTY_MAP = Collections.emptyMap();
|
||||
|
||||
@Override
|
||||
public SecurityAuthentication create(SecurityCredentials credentials) {
|
||||
return new SecurityAuthentication("public", EMPTY_MAP);
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
/* ========================================================================
|
||||
* PlantUML : a free UML diagram generator
|
||||
* ========================================================================
|
||||
*
|
||||
* (C) Copyright 2009-2021, Arnaud Roques
|
||||
*
|
||||
* Project Info: http://plantuml.com
|
||||
*
|
||||
* If you like this project or if you find it useful, you can support us at:
|
||||
*
|
||||
* http://plantuml.com/patreon (only 1$ per month!)
|
||||
* http://plantuml.com/paypal
|
||||
*
|
||||
* This file is part of PlantUML.
|
||||
*
|
||||
* PlantUML 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, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* PlantUML 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 library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
||||
* USA.
|
||||
*
|
||||
*
|
||||
* Original Author: Arnaud Roques
|
||||
*
|
||||
*
|
||||
*/
|
||||
package net.sourceforge.plantuml.security.authentication.basicauth;
|
||||
|
||||
import net.sourceforge.plantuml.StringUtils;
|
||||
import net.sourceforge.plantuml.code.Base64Coder;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityAccessInterceptor;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityAuthentication;
|
||||
|
||||
import java.net.URLConnection;
|
||||
|
||||
/**
|
||||
* Applies from {@link SecurityAuthentication} data a BasicAuth authentication access header.
|
||||
*
|
||||
* @author Aljoscha Rittner
|
||||
*/
|
||||
public class BasicAuthAccessInterceptor implements SecurityAccessInterceptor {
|
||||
|
||||
/**
|
||||
* Applies from {@link SecurityAuthentication} data a BasicAuth authentication access header.
|
||||
* <p>
|
||||
* Expects "identifier" and "secret" to build a Authorization header.
|
||||
*
|
||||
* @param authentication the determined authentication data to authorize for the endpoint access
|
||||
* @param connection the connection to the endpoint
|
||||
*/
|
||||
@Override
|
||||
public void apply(SecurityAuthentication authentication, URLConnection connection) {
|
||||
String auth = getAuth(authentication);
|
||||
String authorization = Base64Coder.encodeString(auth);
|
||||
String authHeaderValue = "Basic " + authorization;
|
||||
connection.setRequestProperty("Authorization", authHeaderValue);
|
||||
}
|
||||
|
||||
private String getAuth(SecurityAuthentication authentication) {
|
||||
String id = (String) authentication.getTokens().get("identifier");
|
||||
char[] secret = (char[]) authentication.getTokens().get("secret");
|
||||
StringBuilder auth = new StringBuilder();
|
||||
if (StringUtils.isNotEmpty(id)) {
|
||||
auth.append(id);
|
||||
if (secret != null && secret.length > 0) {
|
||||
auth.append(':').append(secret);
|
||||
}
|
||||
}
|
||||
return auth.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/* ========================================================================
|
||||
* PlantUML : a free UML diagram generator
|
||||
* ========================================================================
|
||||
*
|
||||
* (C) Copyright 2009-2021, Arnaud Roques
|
||||
*
|
||||
* Project Info: http://plantuml.com
|
||||
*
|
||||
* If you like this project or if you find it useful, you can support us at:
|
||||
*
|
||||
* http://plantuml.com/patreon (only 1$ per month!)
|
||||
* http://plantuml.com/paypal
|
||||
*
|
||||
* This file is part of PlantUML.
|
||||
*
|
||||
* PlantUML 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, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* PlantUML 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 library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
||||
* USA.
|
||||
*
|
||||
*
|
||||
* Original Author: Arnaud Roques
|
||||
*
|
||||
*
|
||||
*/
|
||||
package net.sourceforge.plantuml.security.authentication.basicauth;
|
||||
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityAuthentication;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityAuthorizeManager;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityCredentials;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The {@link BasicAuthAuthorizeManager} creates the authentication on the fly from the credentials without
|
||||
* any access to other services.
|
||||
*
|
||||
* @author Aljoscha Rittner
|
||||
*/
|
||||
public class BasicAuthAuthorizeManager implements SecurityAuthorizeManager {
|
||||
@Override
|
||||
public SecurityAuthentication create(SecurityCredentials credentials) {
|
||||
String type = credentials.getType();
|
||||
String identifier = credentials.getIdentifier();
|
||||
char[] secret = credentials.getSecret();
|
||||
Map<String, Object> tokens = new HashMap<String, Object>();
|
||||
tokens.put("identifier", identifier);
|
||||
if (secret != null) {
|
||||
tokens.put("secret", secret.clone());
|
||||
}
|
||||
return new SecurityAuthentication(type, tokens);
|
||||
}
|
||||
}
|
@ -0,0 +1,165 @@
|
||||
/* ========================================================================
|
||||
* PlantUML : a free UML diagram generator
|
||||
* ========================================================================
|
||||
*
|
||||
* (C) Copyright 2009-2021, Arnaud Roques
|
||||
*
|
||||
* Project Info: http://plantuml.com
|
||||
*
|
||||
* If you like this project or if you find it useful, you can support us at:
|
||||
*
|
||||
* http://plantuml.com/patreon (only 1$ per month!)
|
||||
* http://plantuml.com/paypal
|
||||
*
|
||||
* This file is part of PlantUML.
|
||||
*
|
||||
* PlantUML 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, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* PlantUML 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 library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
||||
* USA.
|
||||
*
|
||||
*
|
||||
* Original Author: Arnaud Roques
|
||||
*
|
||||
*
|
||||
*/
|
||||
package net.sourceforge.plantuml.security.authentication.oauth;
|
||||
|
||||
import net.sourceforge.plantuml.json.Json;
|
||||
import net.sourceforge.plantuml.json.JsonObject;
|
||||
import net.sourceforge.plantuml.json.JsonValue;
|
||||
import net.sourceforge.plantuml.security.SURL;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityAuthentication;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityAuthorizeManager;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.Proxy;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Default abstract OAuth2 AccessAuthorizeManager for OAuth2 managers.
|
||||
*
|
||||
* @author Aljoscha Rittner
|
||||
*/
|
||||
public abstract class AbstractOAuth2AccessAuthorizeManager implements SecurityAuthorizeManager {
|
||||
|
||||
/**
|
||||
* Default headers for token service access.<p>
|
||||
* Initialize with:
|
||||
* <pre>
|
||||
* "Content-Type"="application/x-www-form-urlencoded; charset=UTF-8"
|
||||
* "Accept"="application/json"
|
||||
* </pre>
|
||||
*
|
||||
* @return headers
|
||||
*/
|
||||
protected Map<String, Object> headers() {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
|
||||
map.put("Accept", "application/json");
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the access parameter map.
|
||||
*
|
||||
* @param tokenResponse the JSOn object with the response data
|
||||
* @param tokenType token type to use instead of token_type from response
|
||||
* @return data-map
|
||||
*/
|
||||
protected Map<String, Object> buildAccessDataFromResponse(JsonObject tokenResponse, String tokenType) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
|
||||
toMap(map, tokenResponse, OAuth2Tokens.ACCESS_TOKEN);
|
||||
toMap(map, tokenResponse, OAuth2Tokens.SCOPE);
|
||||
toMap(map, tokenResponse, OAuth2Tokens.EXPIRES_IN);
|
||||
|
||||
if (tokenType == null) {
|
||||
toMap(map, tokenResponse, OAuth2Tokens.TOKEN_TYPE);
|
||||
if (!map.isEmpty() && !map.containsKey(OAuth2Tokens.TOKEN_TYPE.key())) {
|
||||
// default token type is bearer
|
||||
map.put(OAuth2Tokens.TOKEN_TYPE.key(), "bearer");
|
||||
}
|
||||
} else {
|
||||
// Caller don't belief in the token_type response
|
||||
if (!map.isEmpty()) {
|
||||
map.put(OAuth2Tokens.TOKEN_TYPE.key(), tokenType);
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the JSON value to a map key/value.
|
||||
*
|
||||
* @param map collection to store
|
||||
* @param response values from response
|
||||
* @param name name of the value
|
||||
*/
|
||||
private void toMap(Map<String, Object> map, JsonObject response, OAuth2Tokens name) {
|
||||
JsonValue jsonValue = response.get(name.key());
|
||||
if (jsonValue != null && !jsonValue.isNull()) {
|
||||
if (jsonValue.isString()) {
|
||||
map.put(name.key(), jsonValue.asString());
|
||||
} else if (jsonValue.isNumber()) {
|
||||
map.put(name.key(), jsonValue.asInt());
|
||||
} else if (jsonValue.isBoolean()) {
|
||||
map.put(name.key(), jsonValue.asBoolean());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the data to UTF-8 into {@code application/x-www-form-urlencoded}.
|
||||
*
|
||||
* @param data data to encode
|
||||
* @return the encoded data
|
||||
*/
|
||||
protected String urlEncode(String data) {
|
||||
try {
|
||||
return URLEncoder.encode(data, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the endpoint to load the token response and create a SecurityAuthentication.
|
||||
*
|
||||
* @param proxy Proxy for the access
|
||||
* @param grantType grant type
|
||||
* @param tokenType token type to use instead of token_type from response
|
||||
* @param tokenService URL to token service
|
||||
* @param content body content
|
||||
* @param basicAuth principal basicAuth
|
||||
* @return the authentication object to access resources (or null)
|
||||
*/
|
||||
protected SecurityAuthentication requestAndCreateAuthFromResponse(
|
||||
Proxy proxy, String grantType, String tokenType,
|
||||
SURL tokenService, String content, SecurityAuthentication basicAuth) {
|
||||
byte[] bytes = tokenService.getBytesOnPost(proxy, basicAuth, content, headers());
|
||||
if (bytes != null) {
|
||||
JsonValue tokenResponse = Json.parse(new String(bytes, StandardCharsets.UTF_8));
|
||||
if (tokenResponse != null && !tokenResponse.isNull()) {
|
||||
return new SecurityAuthentication("oauth2", null, grantType,
|
||||
buildAccessDataFromResponse(tokenResponse.asObject(), tokenType));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/* ========================================================================
|
||||
* PlantUML : a free UML diagram generator
|
||||
* ========================================================================
|
||||
*
|
||||
* (C) Copyright 2009-2021, Arnaud Roques
|
||||
*
|
||||
* Project Info: http://plantuml.com
|
||||
*
|
||||
* If you like this project or if you find it useful, you can support us at:
|
||||
*
|
||||
* http://plantuml.com/patreon (only 1$ per month!)
|
||||
* http://plantuml.com/paypal
|
||||
*
|
||||
* This file is part of PlantUML.
|
||||
*
|
||||
* PlantUML 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, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* PlantUML 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 library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
||||
* USA.
|
||||
*
|
||||
*
|
||||
* Original Author: Arnaud Roques
|
||||
*
|
||||
*
|
||||
*/
|
||||
package net.sourceforge.plantuml.security.authentication.oauth;
|
||||
|
||||
import net.sourceforge.plantuml.StringUtils;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityAccessInterceptor;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityAuthentication;
|
||||
|
||||
import java.net.URLConnection;
|
||||
|
||||
/**
|
||||
* Applies from {@link SecurityAuthentication} data an OAuth2 Authorization access header.
|
||||
*
|
||||
* @author Aljoscha Rittner
|
||||
*/
|
||||
public class OAuth2AccessInterceptor implements SecurityAccessInterceptor {
|
||||
@Override
|
||||
public void apply(SecurityAuthentication authentication, URLConnection connection) {
|
||||
connection.setRequestProperty("Authorization", getAuth(authentication));
|
||||
}
|
||||
|
||||
private String getAuth(SecurityAuthentication authentication) {
|
||||
String accessToken = (String) authentication.getTokens().get(OAuth2Tokens.ACCESS_TOKEN.key());
|
||||
String type = (String) authentication.getTokens().get(OAuth2Tokens.TOKEN_TYPE.key());
|
||||
return StringUtils.capitalize(type) + ' ' + accessToken;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/* ========================================================================
|
||||
* PlantUML : a free UML diagram generator
|
||||
* ========================================================================
|
||||
*
|
||||
* (C) Copyright 2009-2021, Arnaud Roques
|
||||
*
|
||||
* Project Info: http://plantuml.com
|
||||
*
|
||||
* If you like this project or if you find it useful, you can support us at:
|
||||
*
|
||||
* http://plantuml.com/patreon (only 1$ per month!)
|
||||
* http://plantuml.com/paypal
|
||||
*
|
||||
* This file is part of PlantUML.
|
||||
*
|
||||
* PlantUML 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, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* PlantUML 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 library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
||||
* USA.
|
||||
*
|
||||
*
|
||||
* Original Author: Arnaud Roques
|
||||
*
|
||||
*
|
||||
*/
|
||||
package net.sourceforge.plantuml.security.authentication.oauth;
|
||||
|
||||
import net.sourceforge.plantuml.StringUtils;
|
||||
import net.sourceforge.plantuml.security.SURL;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityAuthentication;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityCredentials;
|
||||
import net.sourceforge.plantuml.security.authentication.basicauth.BasicAuthAuthorizeManager;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Authorize the principal (from {@link SecurityCredentials} and creates a {@link SecurityAuthentication} object with a
|
||||
* bearer token secret.
|
||||
*
|
||||
* @author Aljoscha Rittner
|
||||
*/
|
||||
public class OAuth2ClientAccessAuthorizeManager extends AbstractOAuth2AccessAuthorizeManager {
|
||||
|
||||
/**
|
||||
* Basic Auth manager to access the token service with authorization.
|
||||
*/
|
||||
private final BasicAuthAuthorizeManager basicAuthManager = new BasicAuthAuthorizeManager();
|
||||
|
||||
@Override
|
||||
public SecurityAuthentication create(SecurityCredentials credentials) {
|
||||
String grantType = credentials.getPropertyStr("grantType");
|
||||
String requestScope = credentials.getPropertyStr("scope");
|
||||
String accessTokenUri = credentials.getPropertyStr("accessTokenUri");
|
||||
String tokenType = credentials.getPropertyStr("tokenType");
|
||||
|
||||
// Extra BasicAuth data to access the token service endpoint (if needed)
|
||||
String identifier = credentials.getPropertyStr("credentials.identifier");
|
||||
char[] secret = credentials.getPropertyChars("credentials.secret");
|
||||
|
||||
try {
|
||||
SURL tokenService = SURL.create(accessTokenUri);
|
||||
|
||||
StringBuilder content = new StringBuilder()
|
||||
.append("grant_type=")
|
||||
.append(urlEncode(grantType));
|
||||
if (StringUtils.isNotEmpty(requestScope)) {
|
||||
content.append("&scope=").append(urlEncode(requestScope));
|
||||
}
|
||||
|
||||
SecurityAuthentication basicAuth;
|
||||
if (identifier != null) {
|
||||
// OAuth2 with extra Endpoint BasicAuth credentials
|
||||
basicAuth = basicAuthManager.create(
|
||||
SecurityCredentials.basicAuth(identifier, secret));
|
||||
// We need to add the principal to the form
|
||||
content.append("&client_id").append(urlEncode(credentials.getIdentifier()))
|
||||
.append("&client_secret").append(urlEncode(new String(credentials.getSecret())));
|
||||
} else {
|
||||
// OAuth2 with BasicAuth via principal (standard)
|
||||
basicAuth = basicAuthManager.create(
|
||||
SecurityCredentials.basicAuth(credentials.getIdentifier(), credentials.getSecret()));
|
||||
}
|
||||
|
||||
return requestAndCreateAuthFromResponse(
|
||||
credentials.getProxy(), grantType, tokenType, tokenService, content.toString(), basicAuth);
|
||||
} finally {
|
||||
if (secret != null && secret.length > 0) {
|
||||
Arrays.fill(secret, '*');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
/* ========================================================================
|
||||
* PlantUML : a free UML diagram generator
|
||||
* ========================================================================
|
||||
*
|
||||
* (C) Copyright 2009-2021, Arnaud Roques
|
||||
*
|
||||
* Project Info: http://plantuml.com
|
||||
*
|
||||
* If you like this project or if you find it useful, you can support us at:
|
||||
*
|
||||
* http://plantuml.com/patreon (only 1$ per month!)
|
||||
* http://plantuml.com/paypal
|
||||
*
|
||||
* This file is part of PlantUML.
|
||||
*
|
||||
* PlantUML 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, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* PlantUML 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 library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
||||
* USA.
|
||||
*
|
||||
*
|
||||
* Original Author: Arnaud Roques
|
||||
*
|
||||
*
|
||||
*/
|
||||
package net.sourceforge.plantuml.security.authentication.oauth;
|
||||
|
||||
import net.sourceforge.plantuml.StringUtils;
|
||||
import net.sourceforge.plantuml.security.SURL;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityAuthentication;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityCredentials;
|
||||
import net.sourceforge.plantuml.security.authentication.basicauth.BasicAuthAuthorizeManager;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Authorize via principal a resource owner (from {@link SecurityCredentials} and creates a
|
||||
* {@link SecurityAuthentication} object with a bearer token secret.
|
||||
* <p>
|
||||
* Because a pass through of username/password is an anti-pattern in OAuth2, this authorization method should be
|
||||
* avoided. However, it may be necessary in some environments to gain access with the ROPC flow.
|
||||
*
|
||||
* @author Aljoscha Rittner
|
||||
*/
|
||||
public class OAuth2ResourceOwnerAccessAuthorizeManager extends AbstractOAuth2AccessAuthorizeManager {
|
||||
|
||||
/**
|
||||
* Basic Auth manager to access the token service with authorization.
|
||||
*/
|
||||
private final BasicAuthAuthorizeManager basicAuthManager = new BasicAuthAuthorizeManager();
|
||||
|
||||
@Override
|
||||
public SecurityAuthentication create(SecurityCredentials credentials) {
|
||||
String grantType = credentials.getPropertyStr("grantType");
|
||||
String requestScope = credentials.getPropertyStr("scope");
|
||||
String accessTokenUri = credentials.getPropertyStr("accessTokenUri");
|
||||
String tokenType = credentials.getPropertyStr("tokenType");
|
||||
|
||||
// Resource owner
|
||||
String username = credentials.getPropertyStr("resourceOwner.identifier");
|
||||
char[] password = credentials.getPropertyChars("resourceOwner.secret");
|
||||
|
||||
try {
|
||||
SURL tokenService = SURL.create(accessTokenUri);
|
||||
|
||||
StringBuilder content = new StringBuilder()
|
||||
.append("grant_type=")
|
||||
.append(urlEncode(grantType));
|
||||
if (StringUtils.isNotEmpty(requestScope)) {
|
||||
content.append("&scope=").append(urlEncode(requestScope));
|
||||
}
|
||||
|
||||
// OAuth2 with BasicAuth via principal (standard)
|
||||
SecurityAuthentication basicAuth = basicAuthManager.create(
|
||||
SecurityCredentials.basicAuth(credentials.getIdentifier(), credentials.getSecret()));
|
||||
// We need to add the principal to the form
|
||||
content.append("&username=").append(urlEncode(username))
|
||||
.append("&password=").append(urlEncode(new String(password)));
|
||||
|
||||
return requestAndCreateAuthFromResponse(
|
||||
credentials.getProxy(), grantType, tokenType, tokenService, content.toString(), basicAuth);
|
||||
} finally {
|
||||
if (password != null && password.length > 0) {
|
||||
Arrays.fill(password, '*');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/* ========================================================================
|
||||
* PlantUML : a free UML diagram generator
|
||||
* ========================================================================
|
||||
*
|
||||
* (C) Copyright 2009-2021, Arnaud Roques
|
||||
*
|
||||
* Project Info: http://plantuml.com
|
||||
*
|
||||
* If you like this project or if you find it useful, you can support us at:
|
||||
*
|
||||
* http://plantuml.com/patreon (only 1$ per month!)
|
||||
* http://plantuml.com/paypal
|
||||
*
|
||||
* This file is part of PlantUML.
|
||||
*
|
||||
* PlantUML 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, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* PlantUML 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 library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
||||
* USA.
|
||||
*
|
||||
*
|
||||
* Original Author: Arnaud Roques
|
||||
*
|
||||
*
|
||||
*/
|
||||
package net.sourceforge.plantuml.security.authentication.oauth;
|
||||
|
||||
/**
|
||||
* Some useful constants.
|
||||
*
|
||||
* @author Aljoscha Rittner
|
||||
*/
|
||||
public enum OAuth2Tokens {
|
||||
|
||||
ACCESS_TOKEN("access_token"),
|
||||
|
||||
TOKEN_TYPE("token_type"),
|
||||
|
||||
SCOPE("scope"),
|
||||
|
||||
EXPIRES_IN("expires_in");
|
||||
|
||||
private final String key;
|
||||
|
||||
OAuth2Tokens(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public String key() {
|
||||
return key;
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
/* ========================================================================
|
||||
* PlantUML : a free UML diagram generator
|
||||
* ========================================================================
|
||||
*
|
||||
* (C) Copyright 2009-2021, Arnaud Roques
|
||||
*
|
||||
* Project Info: http://plantuml.com
|
||||
*
|
||||
* If you like this project or if you find it useful, you can support us at:
|
||||
*
|
||||
* http://plantuml.com/patreon (only 1$ per month!)
|
||||
* http://plantuml.com/paypal
|
||||
*
|
||||
* This file is part of PlantUML.
|
||||
*
|
||||
* PlantUML 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, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* PlantUML 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 library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
||||
* USA.
|
||||
*
|
||||
*
|
||||
* Original Author: Arnaud Roques
|
||||
*
|
||||
*
|
||||
*/
|
||||
package net.sourceforge.plantuml.security.authentication.token;
|
||||
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityAccessInterceptor;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityAuthentication;
|
||||
|
||||
import java.net.URLConnection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Applies from {@link SecurityAuthentication} data plain token authentication access headers. This is a raw header
|
||||
* injection with static data.
|
||||
*
|
||||
* @author Aljoscha Rittner
|
||||
*/
|
||||
public class TokenAuthAccessInterceptor implements SecurityAccessInterceptor {
|
||||
|
||||
/**
|
||||
* Applies from {@link SecurityAuthentication} data plain token authentication access headers.
|
||||
* <p>
|
||||
* Expects headers.* key value pairs to pass it directly to the connection.
|
||||
*
|
||||
* @param authentication the determined authentication data to authorize for the endpoint access
|
||||
* @param connection the connection to the endpoint
|
||||
*/
|
||||
@Override
|
||||
public void apply(SecurityAuthentication authentication, URLConnection connection) {
|
||||
|
||||
for (Map.Entry<String, Object> header : authentication.getTokens().entrySet() ) {
|
||||
if (!header.getKey().startsWith("headers.") || header.getValue() == null) {
|
||||
continue;
|
||||
}
|
||||
String key = header.getKey().substring(8);
|
||||
String value = header.getValue().toString();
|
||||
connection.setRequestProperty(key, value);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
/* ========================================================================
|
||||
* PlantUML : a free UML diagram generator
|
||||
* ========================================================================
|
||||
*
|
||||
* (C) Copyright 2009-2021, Arnaud Roques
|
||||
*
|
||||
* Project Info: http://plantuml.com
|
||||
*
|
||||
* If you like this project or if you find it useful, you can support us at:
|
||||
*
|
||||
* http://plantuml.com/patreon (only 1$ per month!)
|
||||
* http://plantuml.com/paypal
|
||||
*
|
||||
* This file is part of PlantUML.
|
||||
*
|
||||
* PlantUML 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, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* PlantUML 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 library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
||||
* USA.
|
||||
*
|
||||
*
|
||||
* Original Author: Arnaud Roques
|
||||
*
|
||||
*
|
||||
*/
|
||||
package net.sourceforge.plantuml.security.authentication.token;
|
||||
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityAuthentication;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityAuthorizeManager;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityCredentials;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The {@link TokenAuthAuthorizeManager} creates the authentication on the fly from the credentials without
|
||||
* any access to other services.
|
||||
*
|
||||
* @author Aljoscha Rittner
|
||||
*/
|
||||
public class TokenAuthAuthorizeManager implements SecurityAuthorizeManager {
|
||||
@Override
|
||||
public SecurityAuthentication create(SecurityCredentials credentials) {
|
||||
return new SecurityAuthentication(credentials.getType(), new HashMap<>(credentials.getProperties()));
|
||||
}
|
||||
}
|
@ -40,6 +40,7 @@ import java.util.Set;
|
||||
|
||||
import net.sourceforge.plantuml.LineLocation;
|
||||
import net.sourceforge.plantuml.OptionFlags;
|
||||
import net.sourceforge.plantuml.security.SecurityUtils;
|
||||
import net.sourceforge.plantuml.tim.EaterException;
|
||||
import net.sourceforge.plantuml.tim.EaterExceptionLocated;
|
||||
import net.sourceforge.plantuml.tim.TContext;
|
||||
@ -71,11 +72,15 @@ public class Getenv extends SimpleReturnFunction {
|
||||
}
|
||||
|
||||
private String getenv(String name) {
|
||||
// Check, if the script requests secret information. A plantuml server should have an own SecurityManager to
|
||||
// avoid access to properties and environment variables, but we should also stop here in other deployments.
|
||||
if (SecurityUtils.isSecurityEnv(name)) {
|
||||
return null;
|
||||
}
|
||||
final String env = System.getProperty(name);
|
||||
if (env != null) {
|
||||
return env;
|
||||
}
|
||||
final String getenv = System.getenv(name);
|
||||
return getenv;
|
||||
return System.getenv(name);
|
||||
}
|
||||
}
|
||||
|
@ -169,7 +169,7 @@ public class PSystemVersion extends PlainStringsDiagram {
|
||||
strings.add("Word Mode");
|
||||
strings.add("Command Line: " + Run.getCommandLine());
|
||||
strings.add("Current Dir: " + new SFile(".").getAbsolutePath());
|
||||
strings.add("plantuml.include.path: " + PreprocessorUtils.getenv("plantuml.include.path"));
|
||||
strings.add("plantuml.include.path: " + PreprocessorUtils.getenv(SecurityUtils.PATHS_INCLUDES));
|
||||
}
|
||||
}
|
||||
strings.add(" ");
|
||||
|
56
test/net/sourceforge/plantuml/security/SFileTest.java
Normal file
56
test/net/sourceforge/plantuml/security/SFileTest.java
Normal file
@ -0,0 +1,56 @@
|
||||
package net.sourceforge.plantuml.security;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests some features of {@link SFile}.
|
||||
*/
|
||||
class SFileTest {
|
||||
|
||||
private static String oldSecurity;
|
||||
|
||||
@TempDir
|
||||
Path tempDir;
|
||||
|
||||
@BeforeAll
|
||||
static void storeSecurityProperty() {
|
||||
oldSecurity = System.getProperty(SecurityUtils.PATHS_SECURITY);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void loadSecurityProperty() {
|
||||
if (oldSecurity != null) {
|
||||
System.setProperty(SecurityUtils.PATHS_SECURITY, oldSecurity);
|
||||
} else {
|
||||
System.getProperties().remove(SecurityUtils.PATHS_SECURITY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks, if we cannot see a secret file in a security folder.
|
||||
*
|
||||
* @throws Exception Hopefully not
|
||||
*/
|
||||
@Test
|
||||
void testFileDenied() throws Exception {
|
||||
File secureFolder = tempDir.toFile();
|
||||
System.setProperty(SecurityUtils.PATHS_SECURITY, secureFolder.getCanonicalPath());
|
||||
|
||||
// A file is needed:
|
||||
File secretFile = File.createTempFile("user", ".credentials", secureFolder);
|
||||
|
||||
assertThat(secretFile).describedAs("File should be visible with standard java.io.File").exists();
|
||||
|
||||
SFile file = new SFile(secretFile.getAbsolutePath());
|
||||
|
||||
assertThat(file.exists()).describedAs("File should be invisible for SFile").isFalse();
|
||||
}
|
||||
}
|
128
test/net/sourceforge/plantuml/security/SURLTest.java
Normal file
128
test/net/sourceforge/plantuml/security/SURLTest.java
Normal file
@ -0,0 +1,128 @@
|
||||
package net.sourceforge.plantuml.security;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Checks some security features
|
||||
*/
|
||||
class SURLTest {
|
||||
|
||||
private static final String EXT = ".credential";
|
||||
|
||||
private static String oldSecurity;
|
||||
|
||||
@TempDir
|
||||
Path tempDir;
|
||||
|
||||
@BeforeAll
|
||||
static void storeSecurityProperty() {
|
||||
oldSecurity = System.getProperty(SecurityUtils.PATHS_SECURITY);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void loadSecurityProperty() {
|
||||
if (oldSecurity != null) {
|
||||
System.setProperty(SecurityUtils.PATHS_SECURITY, oldSecurity);
|
||||
} else {
|
||||
System.getProperties().remove(SecurityUtils.PATHS_SECURITY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a SURL without a Security context.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"http://localhost:8080/api",
|
||||
"http://alice@localhost:8080/api",
|
||||
"http://alice:secret@localhost:8080/api",
|
||||
"https://localhost:8080/api",
|
||||
"https://alice@localhost:8080/api",
|
||||
"https://alice:secret@localhost:8080/api"})
|
||||
void urlWithoutSecurity(String url) {
|
||||
SURL surl = SURL.create(url);
|
||||
|
||||
assertThat(surl).isNotNull();
|
||||
assertThat(surl.isAuthorizationConfigured()).isFalse();
|
||||
|
||||
assertThat(surl).describedAs("URL should be untouched")
|
||||
.hasToString(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a SURL after removing the UserInfo part.
|
||||
*
|
||||
* @throws MalformedURLException this should not be happened
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"http://localhost:8080/api",
|
||||
"http://alice@localhost:8080/api",
|
||||
"http://alice:secret@localhost:8080/api",
|
||||
"https://localhost:8080/api",
|
||||
"https://alice@localhost:8080/api",
|
||||
"https://alice:secret@localhost:8080/api"})
|
||||
void removeUserInfo(String url) throws MalformedURLException {
|
||||
SURL surl = SURL.createWithoutUser(new URL(url));
|
||||
|
||||
assertThat(surl).isNotNull();
|
||||
assertThat(surl.isAuthorizationConfigured()).isFalse();
|
||||
// Check http and https and removed UserInfo part
|
||||
assertThat(surl.toString()).describedAs("User info should be removed from URL")
|
||||
.startsWith("http").endsWith("://localhost:8080/api");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a SURL without a Security context.
|
||||
*
|
||||
* @throws Exception please not
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"http://bob@localhost:8080/api",
|
||||
"https://bob@localhost:8080/api"})
|
||||
void urlWithSecurity(String url) throws Exception {
|
||||
|
||||
File secureFolder = tempDir.toFile();
|
||||
System.setProperty(SecurityUtils.PATHS_SECURITY, secureFolder.getCanonicalPath());
|
||||
|
||||
// A credential file is needed:
|
||||
File secretFile = new File(secureFolder, "bob" + EXT);
|
||||
|
||||
String jsonProxy = "\"proxy\": {\"type\": \"socks\", \"address\": \"192.168.92.250\", \"port\":8080}";
|
||||
String jsonCredentials = "{\"name\": \"bob\", \"identifier\": \"bob\", \"secret\": \"bobssecret\"" +
|
||||
", " + jsonProxy + "}";
|
||||
|
||||
Files.write(secretFile.toPath(), jsonCredentials.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// pre-check, if test can start
|
||||
assertThat(secretFile).describedAs("File should be existing with content")
|
||||
.exists().isNotEmpty();
|
||||
|
||||
assertThat(SecurityUtils.getSecurityPath()).isNotNull();
|
||||
|
||||
// Our test goes here
|
||||
SURL surl = SURL.create(url);
|
||||
|
||||
assertThat(surl).isNotNull();
|
||||
assertThat(surl.isAuthorizationConfigured()).isTrue();
|
||||
|
||||
assertThat(surl.toString()).describedAs("User info should be removed from URL")
|
||||
.startsWith("http").endsWith("://localhost:8080/api");
|
||||
|
||||
secretFile.delete();
|
||||
}
|
||||
}
|
163
test/net/sourceforge/plantuml/security/SecurityUtilsTest.java
Normal file
163
test/net/sourceforge/plantuml/security/SecurityUtilsTest.java
Normal file
@ -0,0 +1,163 @@
|
||||
package net.sourceforge.plantuml.security;
|
||||
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityCredentials;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Checks some aspects in {@link SecurityUtils}.
|
||||
*/
|
||||
class SecurityUtilsTest {
|
||||
|
||||
private static final String EXT = ".credential";
|
||||
|
||||
private static String oldSecurity;
|
||||
|
||||
@TempDir
|
||||
Path tempDir;
|
||||
|
||||
@BeforeAll
|
||||
static void storeSecurityProperty() {
|
||||
oldSecurity = System.getProperty(SecurityUtils.PATHS_SECURITY);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void loadSecurityProperty() {
|
||||
if (oldSecurity != null) {
|
||||
System.setProperty(SecurityUtils.PATHS_SECURITY, oldSecurity);
|
||||
} else {
|
||||
System.getProperties().remove(SecurityUtils.PATHS_SECURITY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks, if SecurityUtils can loadSecurityCredentials.
|
||||
*
|
||||
* @throws Exception nobody wants an exception
|
||||
*/
|
||||
@Test
|
||||
void testLoadOfSecurityCredentials() throws Exception {
|
||||
File secureFolder = tempDir.toFile();
|
||||
System.setProperty(SecurityUtils.PATHS_SECURITY, secureFolder.getCanonicalPath());
|
||||
|
||||
// A file is needed:
|
||||
File secretFile = File.createTempFile("user", EXT, secureFolder);
|
||||
|
||||
String jsonProxy = "\"proxy\": {\"type\": \"socks\", \"address\": \"192.168.92.250\", \"port\":8080}";
|
||||
String jsonCredentials = "{\"name\": \"jenkins\", \"identifier\": \"alice\", \"secret\": \"secret\"" +
|
||||
", " + jsonProxy + "}";
|
||||
|
||||
Files.write(secretFile.toPath(), jsonCredentials.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
assertThat(secretFile).describedAs("File should be existing with content")
|
||||
.exists().isNotEmpty();
|
||||
|
||||
assertThat(SecurityUtils.getSecurityPath()).isNotNull();
|
||||
|
||||
String secretFileName = secretFile.getName();
|
||||
|
||||
SecurityCredentials credentials = SecurityUtils.loadSecurityCredentials(
|
||||
secretFileName.substring(0, secretFileName.length() - EXT.length()));
|
||||
|
||||
assertThat(credentials).isNotNull();
|
||||
|
||||
assertThat(credentials.getName()).isEqualTo("jenkins");
|
||||
assertThat(credentials.getIdentifier()).isEqualTo("alice");
|
||||
assertThat(credentials.getSecret()).isEqualTo(new char[]{'s', 'e', 'c', 'r', 'e', 't'});
|
||||
|
||||
assertThat(credentials.getProxy()).isNotNull();
|
||||
Proxy proxy = credentials.getProxy();
|
||||
assertThat(proxy.type()).isEqualTo(Proxy.Type.SOCKS);
|
||||
|
||||
assertThat(proxy.address()).isNotNull();
|
||||
assertThat(proxy.address()).isInstanceOf(InetSocketAddress.class);
|
||||
|
||||
InetSocketAddress address = (InetSocketAddress) proxy.address();
|
||||
assertThat(address.getPort()).isEqualTo(8080);
|
||||
assertThat(address.getHostString()).isEqualTo("192.168.92.250");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests unsecure names.
|
||||
*
|
||||
* @param name name of filepart
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"_unsecure", "unse%cure", " unsecure04343 ",
|
||||
"tooLong012345678901234567890123456789012345678901234567890123456789unsecure"
|
||||
})
|
||||
void testUnsecureNames(String name) throws IOException {
|
||||
File secureFolder = tempDir.toFile();
|
||||
System.setProperty(SecurityUtils.PATHS_SECURITY, secureFolder.getCanonicalPath());
|
||||
|
||||
// A file is needed:
|
||||
File secretFile = File.createTempFile(name, EXT, secureFolder);
|
||||
|
||||
String jsonProxy = "\"proxy\": {\"type\": \"socks\", \"address\": \"192.168.92.250\", \"port\":8080}";
|
||||
String jsonCredentials = "{\"name\": \"jenkins\", \"identifier\": \"alice\", \"secret\": \"secret\"" +
|
||||
", " + jsonProxy + "}";
|
||||
|
||||
Files.write(secretFile.toPath(), jsonCredentials.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
assertThat(secretFile).describedAs("File should be existing with content")
|
||||
.exists().isNotEmpty();
|
||||
|
||||
String secretFileName = secretFile.getName();
|
||||
|
||||
SecurityCredentials credentials = SecurityUtils.loadSecurityCredentials(
|
||||
secretFileName.substring(0, secretFileName.length() - EXT.length()));
|
||||
|
||||
assertThat(credentials).isEqualTo(SecurityCredentials.NONE);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests secure names.
|
||||
*
|
||||
* @param name name of filepart
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"secure", "Secure", "123secure", "secure123", "1290565234", "45435-543534-fdgfdg"
|
||||
})
|
||||
void testSecureNames(String name) throws IOException {
|
||||
File secureFolder = tempDir.toFile();
|
||||
System.setProperty(SecurityUtils.PATHS_SECURITY, secureFolder.getCanonicalPath());
|
||||
|
||||
// A file is needed:
|
||||
File secretFile = File.createTempFile(name, EXT, secureFolder);
|
||||
|
||||
String jsonProxy = "\"proxy\": {\"type\": \"socks\", \"address\": \"192.168.92.250\", \"port\":8080}";
|
||||
String jsonCredentials = "{\"name\": \"jenkins\", \"identifier\": \"alice\", \"secret\": \"secret\"" +
|
||||
", " + jsonProxy + "}";
|
||||
|
||||
Files.write(secretFile.toPath(), jsonCredentials.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
assertThat(secretFile).describedAs("File should be existing with content")
|
||||
.exists().isNotEmpty();
|
||||
|
||||
String secretFileName = secretFile.getName();
|
||||
|
||||
SecurityCredentials credentials = SecurityUtils.loadSecurityCredentials(
|
||||
secretFileName.substring(0, secretFileName.length() - EXT.length()));
|
||||
|
||||
assertThat(credentials).isNotEqualTo(SecurityCredentials.NONE);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package net.sourceforge.plantuml.security.authentication;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Checks SecurityAuthentication.
|
||||
*/
|
||||
class SecurityAuthenticationTest {
|
||||
|
||||
private static final Map<String, Object> EMPTY_MAP = Collections.emptyMap();
|
||||
|
||||
@Test
|
||||
void isPublicAsPublicTest() {
|
||||
SecurityAuthentication cut = new SecurityAuthentication("public", null, null, EMPTY_MAP);
|
||||
|
||||
assertThat(cut).isNotNull();
|
||||
assertThat(cut.isPublic()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isPublicAsBasicAuthTest() {
|
||||
SecurityAuthentication cut = new SecurityAuthentication("basicauth", null, null, EMPTY_MAP);
|
||||
|
||||
assertThat(cut.isPublic()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getTokensTest() {
|
||||
Map<String, Object> tokens = new HashMap<>();
|
||||
tokens.put("identifier", "alice");
|
||||
tokens.put("secret", new char[]{'s', 'e', 'c', 'r', 'e', 't'});
|
||||
SecurityAuthentication cut = new SecurityAuthentication("basicauth", null, null, tokens);
|
||||
|
||||
assertThat(cut.getTokens())
|
||||
.containsEntry("identifier", "alice")
|
||||
.containsEntry("secret", new char[]{'s', 'e', 'c', 'r', 'e', 't'});
|
||||
}
|
||||
|
||||
@Test
|
||||
void eraseCredentialsTest() {
|
||||
Map<String, Object> tokens = new HashMap<>();
|
||||
tokens.put("identifier", "alice");
|
||||
tokens.put("secret", new char[]{'s', 'e', 'c', 'r', 'e', 't'});
|
||||
SecurityAuthentication cut = new SecurityAuthentication("basicauth", null, null, tokens);
|
||||
|
||||
assertThat(cut.getTokens())
|
||||
.containsEntry("identifier", "alice")
|
||||
.containsEntry("secret", new char[]{'s', 'e', 'c', 'r', 'e', 't'});
|
||||
|
||||
cut.eraseCredentials();
|
||||
assertThat(cut.getTokens()).isEmpty();
|
||||
}
|
||||
}
|
@ -0,0 +1,222 @@
|
||||
package net.sourceforge.plantuml.security.authentication;
|
||||
|
||||
import net.sourceforge.plantuml.json.Json;
|
||||
import net.sourceforge.plantuml.json.JsonValue;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link SecurityCredentials}.
|
||||
*/
|
||||
class SecurityCredentialsTest {
|
||||
|
||||
/**
|
||||
* Tests, if the {@link SecurityCredentials} can be created from JSON.
|
||||
*
|
||||
* @throws Exception hopefully not
|
||||
*/
|
||||
@Test
|
||||
void fromJsonTestComplete() throws Exception {
|
||||
JsonValue jsonValue =
|
||||
Json.parse("{\"name\": \"jenkins\", \"type\": \"basicauth\", " +
|
||||
"\"identifier\": \"alice\", \"secret\": \"secret\"}");
|
||||
SecurityCredentials credentials = SecurityCredentials.fromJson(jsonValue);
|
||||
|
||||
assertThat(credentials).isNotNull().isNotEqualTo(SecurityCredentials.NONE);
|
||||
|
||||
assertThat(credentials.getName()).isEqualTo("jenkins");
|
||||
assertThat(credentials.getType()).isEqualTo("basicauth");
|
||||
assertThat(credentials.getIdentifier()).isEqualTo("alice");
|
||||
assertThat(credentials.getSecret()).isEqualTo(new char[]{'s', 'e', 'c', 'r', 'e', 't'});
|
||||
assertThat(credentials.getProperties()).isEmpty();
|
||||
|
||||
assertThat(credentials.getProxy()).isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests, if the {@link SecurityCredentials} can be created from JSON with direct access.
|
||||
*
|
||||
* @throws Exception hopefully not
|
||||
*/
|
||||
@Test
|
||||
void fromJsonTestCompleteWithProxyDirect() throws Exception {
|
||||
String jsonProxy = "\"proxy\": {\"type\": \"direct\"}";
|
||||
JsonValue jsonValue =
|
||||
Json.parse("{\"name\": \"jenkins\", \"type\": \"basicauth\", " +
|
||||
"\"identifier\": \"alice\", \"secret\": \"secret\"" +
|
||||
", " + jsonProxy + "}");
|
||||
SecurityCredentials credentials = SecurityCredentials.fromJson(jsonValue);
|
||||
|
||||
assertThat(credentials).isNotNull().isNotEqualTo(SecurityCredentials.NONE);
|
||||
|
||||
assertThat(credentials.getName()).isEqualTo("jenkins");
|
||||
assertThat(credentials.getType()).isEqualTo("basicauth");
|
||||
assertThat(credentials.getIdentifier()).isEqualTo("alice");
|
||||
assertThat(credentials.getSecret()).isEqualTo(new char[]{'s', 'e', 'c', 'r', 'e', 't'});
|
||||
assertThat(credentials.getProperties()).isEmpty();
|
||||
|
||||
assertThat(credentials.getProxy()).isNotNull();
|
||||
Proxy proxy = credentials.getProxy();
|
||||
assertThat(proxy.type()).isEqualTo(Proxy.Type.DIRECT);
|
||||
assertThat(proxy.address()).isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests, if the {@link SecurityCredentials} can be created from JSON with socket proxy.
|
||||
*
|
||||
* @throws Exception hopefully not
|
||||
*/
|
||||
@Test
|
||||
void fromJsonTestCompleteWithProxySocksAddress() throws Exception {
|
||||
String jsonProxy = "\"proxy\": {\"type\": \"socks\", \"address\": \"192.168.92.250\", \"port\":8080}";
|
||||
JsonValue jsonValue =
|
||||
Json.parse("{\"name\": \"jenkins\", \"identifier\": \"alice\", \"secret\": \"secret\"" +
|
||||
", " + jsonProxy + "}");
|
||||
SecurityCredentials credentials = SecurityCredentials.fromJson(jsonValue);
|
||||
|
||||
assertThat(credentials).isNotNull().isNotEqualTo(SecurityCredentials.NONE);
|
||||
|
||||
assertThat(credentials.getName()).isEqualTo("jenkins");
|
||||
assertThat(credentials.getType()).as("basicauth should be the default").isEqualTo("basicauth");
|
||||
assertThat(credentials.getIdentifier()).isEqualTo("alice");
|
||||
assertThat(credentials.getSecret()).isEqualTo(new char[]{'s', 'e', 'c', 'r', 'e', 't'});
|
||||
assertThat(credentials.getProperties()).isEmpty();
|
||||
|
||||
assertThat(credentials.getProxy()).isNotNull();
|
||||
Proxy proxy = credentials.getProxy();
|
||||
assertThat(proxy.type()).isEqualTo(Proxy.Type.SOCKS);
|
||||
|
||||
assertThat(proxy.address()).isNotNull();
|
||||
assertThat(proxy.address()).isInstanceOf(InetSocketAddress.class);
|
||||
|
||||
InetSocketAddress address = (InetSocketAddress) proxy.address();
|
||||
assertThat(address.getPort()).isEqualTo(8080);
|
||||
assertThat(address.getHostString()).isEqualTo("192.168.92.250");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests, if the {@link SecurityCredentials} can be created from JSON with http-high-level proxy.
|
||||
*
|
||||
* @throws Exception hopefully not
|
||||
*/
|
||||
@Test
|
||||
void fromJsonTestCompleteWithProxyHttpAddress() throws Exception {
|
||||
String jsonProxy = "\"proxy\": {\"type\": \"http\", \"address\": \"proxy.example.com\", \"port\":8080}";
|
||||
JsonValue jsonValue =
|
||||
Json.parse("{\"name\": \"jenkins\", \"identifier\": \"alice\", \"secret\": \"secret\"" +
|
||||
", " + jsonProxy + "}");
|
||||
SecurityCredentials credentials = SecurityCredentials.fromJson(jsonValue);
|
||||
|
||||
assertThat(credentials).isNotNull().isNotEqualTo(SecurityCredentials.NONE);
|
||||
|
||||
assertThat(credentials.getName()).isEqualTo("jenkins");
|
||||
assertThat(credentials.getType()).as("basicauth should be the default").isEqualTo("basicauth");
|
||||
assertThat(credentials.getIdentifier()).isEqualTo("alice");
|
||||
assertThat(credentials.getSecret()).isEqualTo(new char[]{'s', 'e', 'c', 'r', 'e', 't'});
|
||||
assertThat(credentials.getProperties()).isEmpty();
|
||||
|
||||
assertThat(credentials.getProxy()).isNotNull();
|
||||
Proxy proxy = credentials.getProxy();
|
||||
assertThat(proxy.type()).isEqualTo(Proxy.Type.HTTP);
|
||||
|
||||
assertThat(proxy.address()).isNotNull();
|
||||
assertThat(proxy.address()).isInstanceOf(InetSocketAddress.class);
|
||||
|
||||
InetSocketAddress address = (InetSocketAddress) proxy.address();
|
||||
assertThat(address.getPort()).isEqualTo(8080);
|
||||
assertThat(address.getHostString()).isEqualTo("proxy.example.com");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests, if the {@link SecurityCredentials} can be created from JSON.
|
||||
*
|
||||
* @throws Exception hopefully not
|
||||
*/
|
||||
@Test
|
||||
void fromJsonTokenTest() throws Exception {
|
||||
|
||||
String headers = "{\"Authorization\": \"ApiKey a4db08b7-5729-4ba9-8c08-f2df493465a1\"}";
|
||||
String properties = "{\"headers\": " + headers + "}";
|
||||
JsonValue jsonValue =
|
||||
Json.parse("{\"name\": \"github\", \"type\": \"tokenauth\", " +
|
||||
"\"properties\": " + properties + "}");
|
||||
SecurityCredentials credentials = SecurityCredentials.fromJson(jsonValue);
|
||||
|
||||
assertThat(credentials).isNotNull().isNotEqualTo(SecurityCredentials.NONE);
|
||||
|
||||
assertThat(credentials.getName()).isEqualTo("github");
|
||||
assertThat(credentials.getType()).isEqualTo("tokenauth");
|
||||
assertThat(credentials.getProperties())
|
||||
.isNotEmpty().containsEntry("headers.Authorization", "ApiKey a4db08b7-5729-4ba9-8c08-f2df493465a1");
|
||||
|
||||
assertThat(credentials.getProxy()).isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests, if the {@link SecurityCredentials} can be created from JSON with empty password.
|
||||
*
|
||||
* @throws Exception hopefully not
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"{\"name\": \"jenkins\", \"identifier\": \"alice\", \"secret\": null}", // null password
|
||||
"{\"name\": \"jenkins\", \"identifier\": \"alice\", \"secret\": \"\"}", // empty password
|
||||
"{\"name\": \"jenkins\", \"identifier\": \"alice\"}", // no password
|
||||
"{\"name\": \"jenkins\", \"identifier\": \"alice\", \"pwd\": \"Xyz\"}" // pwd ignored
|
||||
})
|
||||
void fromJsonTestNoPassword(String json) throws Exception {
|
||||
JsonValue jsonValue = Json.parse(json);
|
||||
SecurityCredentials credentials = SecurityCredentials.fromJson(jsonValue);
|
||||
|
||||
assertThat(credentials).isNotNull().isNotEqualTo(SecurityCredentials.NONE);
|
||||
|
||||
assertThat(credentials.getName()).isEqualTo("jenkins");
|
||||
assertThat(credentials.getIdentifier()).isEqualTo("alice");
|
||||
assertThat(credentials.getSecret()).isNull();
|
||||
assertThat(credentials.getProperties()).isEmpty();
|
||||
|
||||
assertThat(credentials.getProxy()).isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks, if the property parser can read simple values.
|
||||
*/
|
||||
@Test
|
||||
void fromJsonWithSimpleProperties() {
|
||||
String props = "{\"grantType\": \"client_credentials\", \"test\": true, \"number\": 1.0, \"x\": null}";
|
||||
String json = "{\"name\": \"jenkins\", \"identifier\": \"alice\", \"properties\": " + props + " }";
|
||||
JsonValue jsonValue = Json.parse(json);
|
||||
SecurityCredentials credentials = SecurityCredentials.fromJson(jsonValue);
|
||||
|
||||
assertThat(credentials.getProperties())
|
||||
.isNotEmpty()
|
||||
.containsEntry("grantType", "client_credentials")
|
||||
.containsEntry("test", Boolean.TRUE)
|
||||
.containsEntry("number", 1.0d)
|
||||
.doesNotContainKey("x");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks, if the property parser can read nested values.
|
||||
*/
|
||||
@Test
|
||||
void fromJsonWithNestedProperties() {
|
||||
String nested = "{\"identifier\": \"serviceId\",\"secret\": \"ServiceSecret\"}";
|
||||
String props = "{\"grantType\": \"client_credentials\", \"nested\": " + nested + "}";
|
||||
String json = "{\"name\": \"jenkins\", \"identifier\": \"alice\", \"properties\": " + props + " }";
|
||||
JsonValue jsonValue = Json.parse(json);
|
||||
SecurityCredentials credentials = SecurityCredentials.fromJson(jsonValue);
|
||||
|
||||
assertThat(credentials.getProperties())
|
||||
.isNotEmpty()
|
||||
.containsEntry("grantType", "client_credentials")
|
||||
.containsEntry("nested.identifier", "serviceId")
|
||||
.containsEntry("nested.secret", "ServiceSecret");
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package net.sourceforge.plantuml.security.authentication;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class SecurityDefaultNoopAuthorizeManagerTest {
|
||||
|
||||
/**
|
||||
* Tests the creation of SecurityAuthentication via SecurityDefaultNoopAuthenticationInterceptor.
|
||||
*/
|
||||
@Test
|
||||
void createTest() {
|
||||
SecurityAuthorizeManager cut = new SecurityDefaultNoopAuthorizeManager();
|
||||
|
||||
SecurityAuthentication securityAuthentication = cut.create(null);
|
||||
|
||||
assertThat(securityAuthentication).isNotNull();
|
||||
|
||||
assertThat(securityAuthentication.isPublic()).isTrue();
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package net.sourceforge.plantuml.security.authentication.basicauth;
|
||||
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityAuthentication;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityAuthorizeManager;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityCredentials;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class BasicAuthAuthorizeManagerTest {
|
||||
|
||||
/**
|
||||
* Tests the creation of SecurityAuthentication via BasicAuthAuthorizeManager.
|
||||
*/
|
||||
@Test
|
||||
void createTest() {
|
||||
SecurityAuthorizeManager cut = new BasicAuthAuthorizeManager();
|
||||
|
||||
SecurityAuthentication securityAuthentication = cut.create(
|
||||
SecurityCredentials.basicAuth("alice", new char[]{'s', 'e', 'c', 'r', 'e', 't'}));
|
||||
|
||||
assertThat(securityAuthentication).isNotNull();
|
||||
|
||||
assertThat(securityAuthentication.isPublic()).isFalse();
|
||||
assertThat(securityAuthentication.getTokens())
|
||||
.containsEntry("identifier", "alice")
|
||||
.containsEntry("secret", new char[]{'s', 'e', 'c', 'r', 'e', 't'});
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
package net.sourceforge.plantuml.security.authentication.oauth;
|
||||
|
||||
import net.sourceforge.plantuml.json.Json;
|
||||
import net.sourceforge.plantuml.json.JsonObject;
|
||||
import net.sourceforge.plantuml.json.JsonValue;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityAuthentication;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityCredentials;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class AbstractOAuth2AccessAuthorizeManagerTest {
|
||||
|
||||
private final MockedOAuth2AccessAuthorizeManager cut = new MockedOAuth2AccessAuthorizeManager();
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"{\"access_token\":\"7fea8201-eebb-4101-a76f-ddc1efdd3bbd\",\"scope\":\"read write\"," +
|
||||
"\"token_type\":\"bearer\",\"expires_in\":300}",
|
||||
"{\"access_token\":\"7fea8201-eebb-4101-a76f-ddc1efdd3bbd\",\"scope\":\"read write\",\"expires_in\":300}"
|
||||
})
|
||||
void accessDataTest(String jsonResponse) {
|
||||
JsonValue response = Json.parse(jsonResponse);
|
||||
Map<String, Object> responseMap = cut.buildAccessDataFromResponse(response.asObject(), null);
|
||||
|
||||
assertThat(responseMap)
|
||||
.containsEntry(OAuth2Tokens.ACCESS_TOKEN.key(), "7fea8201-eebb-4101-a76f-ddc1efdd3bbd")
|
||||
.containsEntry(OAuth2Tokens.SCOPE.key(), "read write")
|
||||
.containsEntry(OAuth2Tokens.TOKEN_TYPE.key(), "bearer")
|
||||
.containsEntry(OAuth2Tokens.EXPIRES_IN.key(), 300);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"{\"access_token\":\"7fea8201-eebb-4101-a76f-ddc1efdd3bbd\",\"scope\":\"read write\"," +
|
||||
"\"token_type\":\"bearer\",\"expires_in\":300}",
|
||||
"{\"access_token\":\"7fea8201-eebb-4101-a76f-ddc1efdd3bbd\",\"scope\":\"read write\",\"expires_in\":300}"
|
||||
})
|
||||
void accessDataOverrideTokenTypeTest(String jsonResponse) {
|
||||
JsonValue response = Json.parse(jsonResponse);
|
||||
Map<String, Object> responseMap = cut.buildAccessDataFromResponse(response.asObject(), "apikey");
|
||||
|
||||
assertThat(responseMap)
|
||||
.containsEntry(OAuth2Tokens.ACCESS_TOKEN.key(), "7fea8201-eebb-4101-a76f-ddc1efdd3bbd")
|
||||
.containsEntry(OAuth2Tokens.SCOPE.key(), "read write")
|
||||
.containsEntry(OAuth2Tokens.TOKEN_TYPE.key(), "apikey")
|
||||
.containsEntry(OAuth2Tokens.EXPIRES_IN.key(), 300);
|
||||
}
|
||||
|
||||
@Test
|
||||
void accessDataEmptyTest() {
|
||||
String jsonResponse = "{}";
|
||||
JsonValue response = Json.parse(jsonResponse);
|
||||
Map<String, Object> responseMap = cut.buildAccessDataFromResponse(response.asObject(), null);
|
||||
|
||||
assertThat(responseMap).as("Empty map should not contain default token-type 'bearer'").isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void accessDataEmptyAndTokenOverrideTest() {
|
||||
String jsonResponse = "{}";
|
||||
JsonValue response = Json.parse(jsonResponse);
|
||||
Map<String, Object> responseMap = cut.buildAccessDataFromResponse(response.asObject(), "apikey");
|
||||
|
||||
assertThat(responseMap).as("Empty map should not contain override token-type 'apikey'").isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void urlEncodeTest() {
|
||||
assertThat(cut.urlEncode("alice")).isEqualTo("alice");
|
||||
assertThat(cut.urlEncode("bob")).isEqualTo("bob");
|
||||
assertThat(cut.urlEncode("alice and bob")).isEqualTo("alice+and+bob");
|
||||
assertThat(cut.urlEncode("Müller")).isEqualTo("M%C3%BCller");
|
||||
assertThat(cut.urlEncode("s?ecret=-110%")).isEqualTo("s%3Fecret%3D-110%25");
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock to make methods public for testing.
|
||||
*/
|
||||
static class MockedOAuth2AccessAuthorizeManager extends AbstractOAuth2AccessAuthorizeManager {
|
||||
|
||||
@Override
|
||||
public SecurityAuthentication create(SecurityCredentials credentials) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> buildAccessDataFromResponse(JsonObject tokenResponse, String overrideTokenType) {
|
||||
return super.buildAccessDataFromResponse(tokenResponse, overrideTokenType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String urlEncode(String data) {
|
||||
return super.urlEncode(data);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package net.sourceforge.plantuml.security.authentication.token;
|
||||
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityAuthentication;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityAuthorizeManager;
|
||||
import net.sourceforge.plantuml.security.authentication.SecurityCredentials;
|
||||
import net.sourceforge.plantuml.security.authentication.basicauth.BasicAuthAuthorizeManager;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class TokenAuthAuthorizeManagerTest {
|
||||
|
||||
/**
|
||||
* Tests the creation of SecurityAuthentication via {@link TokenAuthAuthorizeManager}.
|
||||
*/
|
||||
@Test
|
||||
void createSimpleTest() {
|
||||
SecurityAuthorizeManager cut = new TokenAuthAuthorizeManager();
|
||||
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put("headers.Authorization", "ApiKey a4db08b7-5729-4ba9-8c08-f2df493465a1");
|
||||
SecurityCredentials credentials = new SecurityCredentials("test", "token", null, null,
|
||||
properties, Proxy.NO_PROXY);
|
||||
|
||||
SecurityAuthentication securityAuthentication = cut.create(credentials);
|
||||
|
||||
assertThat(securityAuthentication).isNotNull();
|
||||
|
||||
assertThat(securityAuthentication.isPublic()).isFalse();
|
||||
assertThat(securityAuthentication.getTokens())
|
||||
.containsEntry("headers.Authorization", "ApiKey a4db08b7-5729-4ba9-8c08-f2df493465a1");
|
||||
}
|
||||
}
|
59
test/net/sourceforge/plantuml/tim/stdlib/GetenvTest.java
Normal file
59
test/net/sourceforge/plantuml/tim/stdlib/GetenvTest.java
Normal file
@ -0,0 +1,59 @@
|
||||
package net.sourceforge.plantuml.tim.stdlib;
|
||||
|
||||
import net.sourceforge.plantuml.tim.EaterException;
|
||||
import net.sourceforge.plantuml.tim.EaterExceptionLocated;
|
||||
import net.sourceforge.plantuml.tim.expression.TValue;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests the internal function %getenv.
|
||||
*/
|
||||
class GetenvTest {
|
||||
|
||||
/**
|
||||
* Tests getenv should not publish plantuml.security.* environment variables.
|
||||
*
|
||||
* @throws EaterException should not
|
||||
* @throws EaterExceptionLocated should not
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"plantuml.security.blabla",
|
||||
"plantuml.SECURITY.blabla",
|
||||
"plantuml.security.credentials.path",
|
||||
})
|
||||
void executeReturnFunctionSecurityTest(String name) throws EaterException, EaterExceptionLocated {
|
||||
System.setProperty("plantuml.security.blabla", "example");
|
||||
Getenv cut = new Getenv();
|
||||
|
||||
List<TValue> values = Collections.singletonList(TValue.fromString(name));
|
||||
TValue tValue = cut.executeReturnFunction(null, null, null, values, null);
|
||||
assertThat (tValue.toString()).isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getenv still returns 'good' variables.
|
||||
*
|
||||
* @throws EaterException should not
|
||||
* @throws EaterExceptionLocated should not
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {
|
||||
"java.version",
|
||||
"path.separator",
|
||||
"line.separator",
|
||||
})
|
||||
void executeReturnFunctionTest(String name) throws EaterException, EaterExceptionLocated {
|
||||
Getenv cut = new Getenv();
|
||||
|
||||
List<TValue> values = Collections.singletonList(TValue.fromString(name));
|
||||
TValue tValue = cut.executeReturnFunction(null, null, null, values, null);
|
||||
assertThat (tValue.toString()).isNotEmpty();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user