diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/js/app.js b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/js/app.js index e58c5f55d7..bf71b437ad 100755 --- a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/js/app.js +++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/src/main/webapp/js/app.js @@ -62,7 +62,16 @@ module.controller('TokenCtrl', function ($scope, Identity) { } $scope.requestEntitlements = function () { - Identity.authorization.entitlement('photoz-restful-api').then(function (rpt) {}); + Identity.authorization.entitlement('photoz-restful-api').then(function (rpt) { + document.getElementById("output").innerHTML = JSON.stringify(jwt_decode(rpt), null, ' '); + }); + } + + $scope.requestEntitlement = function () { + var param={"permissions" : [{"resource_set_name" : "Album Resource"}]}; + Identity.authorization.entitlement('photoz-restful-api', param).then(function (rpt) { + document.getElementById("output").innerHTML = JSON.stringify(jwt_decode(rpt), null, ' '); + }); } $scope.Identity = Identity; diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api-authz-service.json b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api-authz-service.json index 1ce85dd9dc..3807df7de6 100644 --- a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api-authz-service.json +++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api-authz-service.json @@ -47,7 +47,7 @@ "logic": "POSITIVE", "decisionStrategy": "UNANIMOUS", "config": { - "mavenArtifactVersion": "2.1.0-SNAPSHOT", + "mavenArtifactVersion": "2.5.0.Final-SNAPSHOT", "mavenArtifactId": "photoz-authz-policy", "sessionName": "MainOwnerSession", "mavenArtifactGroupId": "org.keycloak.testsuite", diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/WEB-INF/web.xml b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/WEB-INF/web.xml index 14d0615978..2378ca59b5 100644 --- a/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/WEB-INF/web.xml +++ b/testsuite/integration-arquillian/test-apps/servlet-authz/src/main/webapp/WEB-INF/web.xml @@ -13,16 +13,8 @@ user - - - - - - All Resources - /* - - admin + user_premium @@ -39,6 +31,10 @@ user + + user_premium + + 403 /accessDenied.jsp diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/ProfileAssume.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/ProfileAssume.java index 7fbf59b068..9dd598f299 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/ProfileAssume.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/ProfileAssume.java @@ -26,11 +26,11 @@ import org.keycloak.common.Profile; public class ProfileAssume { public static void assumePreview() { - Assume.assumeTrue("Ignoring test as community/preview profile is not enabled", Profile.isPreviewEnabled()); + Assume.assumeTrue("Ignoring test as community/preview profile is not enabled", !Profile.getName().equals("product")); } public static void assumePreviewDisabled() { - Assume.assumeFalse("Ignoring test as community/preview profile is enabled", Profile.isPreviewEnabled()); + Assume.assumeFalse("Ignoring test as community/preview profile is enabled", !Profile.getName().equals("product")); } } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java index fe98f0d6c6..b721166720 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/PhotozClientAuthzTestApp.java @@ -54,7 +54,13 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl { @FindBy(xpath = "//a[@ng-click = 'Identity.logout()']") WebElement signOutButton; - + + @FindBy(id = "entitlement") + WebElement entitlement; + + @FindBy(id = "entitlements") + WebElement entitlements; + public void createAlbum(String name) { navigateTo(); this.driver.findElement(By.id("create-album")).click(); @@ -85,6 +91,16 @@ public class PhotozClientAuthzTestApp extends AbstractPageWithInjectedUrl { signOutButton.click(); // Sometimes doesn't work in PhantomJS! pause(WAIT_AFTER_OPERATION); } + + public void requestEntitlement() { + entitlement.click(); + pause(WAIT_AFTER_OPERATION); + } + + public void requestEntitlements() { + entitlements.click(); + pause(WAIT_AFTER_OPERATION); + } public void login(String username, String password, String... scopes) { if (scopes.length > 0) { diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/provider/URLProvider.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/provider/URLProvider.java index 77640aada5..e8852bc151 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/provider/URLProvider.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/provider/URLProvider.java @@ -74,7 +74,7 @@ public class URLProvider extends URLResourceProvider { } try { - if (System.getProperty("app.server","").startsWith("eap6")) { + if (System.getProperty("app.server.management.protocol","").equals("remote")) { if (url == null) { url = new URL("http://localhost:8080/"); } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/KcAdmExec.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/KcAdmExec.java new file mode 100644 index 0000000000..3ab6b342b0 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/KcAdmExec.java @@ -0,0 +1,58 @@ +package org.keycloak.testsuite.cli; + +import org.keycloak.testsuite.cli.exec.AbstractExec; +import org.keycloak.testsuite.cli.exec.AbstractExecBuilder; + +import java.io.InputStream; + +/** + * @author Marko Strukelj + */ +public class KcAdmExec extends AbstractExec { + + public static final String WORK_DIR = System.getProperty("user.dir") + "/target/containers/keycloak-client-tools"; + + public static final String CMD = OS_ARCH.isWindows() ? "kcadm.bat" : "kcadm.sh"; + + private KcAdmExec(String workDir, String argsLine, InputStream stdin) { + this(workDir, argsLine, null, stdin); + } + + private KcAdmExec(String workDir, String argsLine, String env, InputStream stdin) { + super(workDir, argsLine, env, stdin); + } + + @Override + public String getCmd() { + return "bin/" + CMD; + } + + public static KcAdmExec.Builder newBuilder() { + return (KcAdmExec.Builder) new KcAdmExec.Builder().workDir(WORK_DIR); + } + + public static KcAdmExec execute(String args) { + return newBuilder() + .argsLine(args) + .execute(); + } + + public static class Builder extends AbstractExecBuilder { + + @Override + public KcAdmExec execute() { + KcAdmExec exe = new KcAdmExec(workDir, argsLine, env, stdin); + exe.dumpStreams = dumpStreams; + exe.execute(); + return exe; + } + + @Override + public KcAdmExec executeAsync() { + KcAdmExec exe = new KcAdmExec(workDir, argsLine, env, stdin); + exe.dumpStreams = dumpStreams; + exe.executeAsync(); + return exe; + } + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/KcRegExec.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/KcRegExec.java index 5f20c2a006..56039afefc 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/KcRegExec.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/KcRegExec.java @@ -1,5 +1,8 @@ package org.keycloak.testsuite.cli; +import org.keycloak.testsuite.cli.exec.AbstractExec; +import org.keycloak.testsuite.cli.exec.AbstractExecBuilder; + import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -18,57 +21,27 @@ import java.util.concurrent.TimeUnit; /** * @author Marko Strukelj */ -public class KcRegExec { +public class KcRegExec extends AbstractExec { public static final String WORK_DIR = System.getProperty("user.dir") + "/target/containers/keycloak-client-tools"; - public static final OsArch OS_ARCH = OsUtils.determineOSAndArch(); - public static final String CMD = OS_ARCH.isWindows() ? "kcreg.bat" : "kcreg.sh"; - private long waitTimeout = 30000; - - private Process process; - - private int exitCode = -1; - - private boolean logStreams = Boolean.valueOf(System.getProperty("cli.log.output", "true")); - - private boolean dumpStreams; - - private String workDir = WORK_DIR; - - private String env; - - private String argsLine; - - private ByteArrayOutputStream stdout = new ByteArrayOutputStream(); - - private ByteArrayOutputStream stderr = new ByteArrayOutputStream(); - - private InputStream stdin = new InteractiveInputStream(); - - private Throwable err; - private KcRegExec(String workDir, String argsLine, InputStream stdin) { this(workDir, argsLine, null, stdin); } private KcRegExec(String workDir, String argsLine, String env, InputStream stdin) { - if (workDir != null) { - this.workDir = workDir; - } - - this.argsLine = argsLine; - this.env = env; - - if (stdin != null) { - this.stdin = stdin; - } + super(workDir, argsLine, env, stdin); } - public static Builder newBuilder() { - return new Builder(); + @Override + public String getCmd() { + return "bin/" + CMD; + } + + public static KcRegExec.Builder newBuilder() { + return (KcRegExec.Builder) new KcRegExec.Builder().workDir(WORK_DIR); } public static KcRegExec execute(String args) { @@ -77,225 +50,9 @@ public class KcRegExec { .execute(); } - public void execute() { - executeAsync(); - if (err == null) { - waitCompletion(); - } - } - - - public void executeAsync() { - - try { - if (OS_ARCH.isWindows()) { - String cmd = (env != null ? "set " + env + " & " : "") + "bin\\" + CMD + " " + fixQuotes(argsLine); - System.out.println("Executing: cmd.exe /c " + cmd); - process = Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", cmd}, null, new File(workDir)); - } else { - String cmd = (env != null ? env + " " : "") + "bin/" + CMD + " " + argsLine; - System.out.println("Executing: sh -c " + cmd); - process = Runtime.getRuntime().exec(new String[]{"sh", "-c", cmd}, null, new File(workDir)); - } - - new StreamReaderThread(process.getInputStream(), logStreams ? new LoggingOutputStream("STDOUT", stdout) : stdout) - .start(); - - new StreamReaderThread(process.getErrorStream(), logStreams ? new LoggingOutputStream("STDERR", stderr) : stderr) - .start(); - - new StreamReaderThread(stdin, process.getOutputStream()) - .start(); - - } catch (Throwable t) { - err = t; - } - } - - private String fixQuotes(String argsLine) { - argsLine = argsLine + " "; - argsLine = argsLine.replaceAll("\"", "\\\\\""); - argsLine = argsLine.replaceAll(" '", " \""); - argsLine = argsLine.replaceAll("' ", "\" "); - return argsLine; - } - - public void waitCompletion() { - - //if (stdin instanceof InteractiveInputStream) { - // ((InteractiveInputStream) stdin).close(); - //} - try { - if (process.waitFor(waitTimeout, TimeUnit.MILLISECONDS)) { - exitCode = process.exitValue(); - if (exitCode != 0) { - dumpStreams = true; - } - } else { - if (process.isAlive()) { - process.destroyForcibly(); - } - throw new RuntimeException("Timeout after " + (waitTimeout / 1000) + " seconds."); - } - } catch (InterruptedException e) { - dumpStreams = true; - throw new RuntimeException("Interrupted ...", e); - } catch (Throwable t) { - dumpStreams = true; - err = t; - } finally { - if (!logStreams && dumpStreams) try { - System.out.println("STDOUT: "); - copyStream(new ByteArrayInputStream(stdout.toByteArray()), System.out); - System.out.println("STDERR: "); - copyStream(new ByteArrayInputStream(stderr.toByteArray()), System.out); - } catch (Exception ignored) { - } - } - } - - public int exitCode() { - return exitCode; - } - - public Throwable error() { - return err; - } - - public InputStream stdout() { - return new ByteArrayInputStream(stdout.toByteArray()); - } - - public List stdoutLines() { - return parseStreamAsLines(new ByteArrayInputStream(stdout.toByteArray())); - } - - public String stdoutString() { - return new String(stdout.toByteArray()); - } - - public InputStream stderr() { - return new ByteArrayInputStream(stderr.toByteArray()); - } - - public List stderrLines() { - return parseStreamAsLines(new ByteArrayInputStream(stderr.toByteArray())); - } - - public String stderrString() { - return new String(stderr.toByteArray()); - } - - static List parseStreamAsLines(InputStream stream) { - List lines = new ArrayList<>(); - try { - BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); - - String line; - while ((line = reader.readLine()) != null) { - lines.add(line); - } - return lines; - } catch (IOException e) { - throw new RuntimeException("Unexpected I/O error", e); - } - } - - public void waitForStdout(String content) { - long start = System.currentTimeMillis(); - while (System.currentTimeMillis() - start < waitTimeout) { - if (stdoutString().indexOf(content) != -1) { - return; - } - try { - Thread.sleep(10); - } catch (InterruptedException e) { - throw new RuntimeException("Interrupted ...", e); - } - } - - throw new RuntimeException("Timed while waiting for content to appear in stdout"); - } - - public void sendToStdin(String s) { - if (stdin instanceof InteractiveInputStream) { - ((InteractiveInputStream) stdin).pushBytes(s.getBytes()); - } else { - throw new RuntimeException("Can't push to stdin - not interactive"); - } - } - - static class StreamReaderThread extends Thread { - - private InputStream is; - private OutputStream os; - - StreamReaderThread(InputStream is, OutputStream os) { - this.is = is; - this.os = os; - } - - public void run() { - try { - copyStream(is, os); - } catch (IOException e) { - throw new RuntimeException("Unexpected I/O error", e); - } finally { - try { - os.close(); - } catch (IOException ignored) { - System.err.print("IGNORED: error while closing output stream: "); - ignored.printStackTrace(); - } - } - } - } - - static void copyStream(InputStream is, OutputStream os) throws IOException { - byte [] buf = new byte[8192]; - - try (InputStream iss = is) { - int c; - while ((c = iss.read(buf)) != -1) { - os.write(buf, 0, c); - os.flush(); - } - } - } - - public static class Builder { - - private String workDir; - private String argsLine; - private InputStream stdin; - private String env; - private boolean dumpStreams; - - public Builder workDir(String path) { - this.workDir = path; - return this; - } - - public Builder argsLine(String cmd) { - this.argsLine = cmd; - return this; - } - - public Builder stdin(InputStream is) { - this.stdin = is; - return this; - } - - public Builder env(String env) { - this.env = env; - return this; - } - - public Builder fullStreamDump() { - this.dumpStreams = true; - return this; - } + public static class Builder extends AbstractExecBuilder { + @Override public KcRegExec execute() { KcRegExec exe = new KcRegExec(workDir, argsLine, env, stdin); exe.dumpStreams = dumpStreams; @@ -303,6 +60,7 @@ public class KcRegExec { return exe; } + @Override public KcRegExec executeAsync() { KcRegExec exe = new KcRegExec(workDir, argsLine, env, stdin); exe.dumpStreams = dumpStreams; @@ -311,177 +69,4 @@ public class KcRegExec { } } - static class NullInputStream extends InputStream { - - @Override - public int read() throws IOException { - return -1; - } - } - - static class InteractiveInputStream extends InputStream { - - private LinkedList queue = new LinkedList<>(); - - private Thread consumer; - - private boolean closed; - - @Override - public int read(byte b[]) throws IOException { - return read(b, 0, b.length); - } - - @Override - public synchronized int read(byte[] b, int off, int len) throws IOException { - - Byte current = null; - int rc = 0; - try { - consumer = Thread.currentThread(); - - do { - current = queue.poll(); - if (current == null) { - if (rc > 0) { - return rc; - } else { - do { - if (closed) { - return -1; - } - wait(); - } - while ((current = queue.poll()) == null); - } - } - - b[off + rc] = current; - rc++; - } while (rc < len); - - } catch (InterruptedException e) { - throw new InterruptedIOException("Signalled to exit"); - } finally { - consumer = null; - } - return rc; - } - - @Override - public long skip(long n) throws IOException { - return super.skip(n); - } - - @Override - public int available() throws IOException { - return super.available(); - } - - @Override - public synchronized void mark(int readlimit) { - super.mark(readlimit); - } - - @Override - public synchronized void reset() throws IOException { - super.reset(); - } - - @Override - public boolean markSupported() { - return super.markSupported(); - } - - @Override - public synchronized int read() throws IOException { - if (closed) { - return -1; - } - - // when input is available pass it on - Byte current; - try { - consumer = Thread.currentThread(); - - while ((current = queue.poll()) == null) { - wait(); - if (closed) { - return -1; - } - } - - } catch (InterruptedException e) { - throw new InterruptedIOException("Signalled to exit"); - } finally { - consumer = null; - } - return current; - } - - @Override - public synchronized void close() { - closed = true; - new RuntimeException("IIS || close").printStackTrace(); - if (consumer != null) { - consumer.interrupt(); - } - } - - public synchronized void pushBytes(byte [] buff) { - for (byte b : buff) { - queue.add(b); - } - notify(); - } - } - - - static class LoggingOutputStream extends FilterOutputStream { - - private ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - private String name; - - public LoggingOutputStream(String name, OutputStream os) { - super(os); - this.name = name; - } - - @Override - public void write(int b) throws IOException { - super.write(b); - if (b == 10) { - log(); - } else { - buffer.write(b); - } - } - - @Override - public void write(byte[] buf) throws IOException { - write(buf, 0, buf.length); - } - - @Override - public void write(byte[] buf, int offs, int len) throws IOException { - for (int i = 0; i < len; i++) { - write(buf[offs+i]); - } - } - - @Override - public void close() throws IOException { - super.close(); - if (buffer.size() > 0) { - log(); - } - } - - private void log() { - String log = new String(buffer.toByteArray()); - buffer.reset(); - System.out.println("[" + name + "] " + log); - } - } - } \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/AbstractExec.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/AbstractExec.java new file mode 100644 index 0000000000..91fcb1318d --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/AbstractExec.java @@ -0,0 +1,245 @@ +package org.keycloak.testsuite.cli.exec; + +import org.keycloak.testsuite.cli.OsArch; +import org.keycloak.testsuite.cli.OsUtils; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * @author Marko Strukelj + */ +public abstract class AbstractExec { + + public static final String WORK_DIR = System.getProperty("user.dir"); + + public static final OsArch OS_ARCH = OsUtils.determineOSAndArch(); + + private long waitTimeout = 30000; + + private Process process; + + private int exitCode = -1; + + private boolean logStreams = Boolean.valueOf(System.getProperty("cli.log.output", "true")); + + protected boolean dumpStreams; + + protected String workDir = WORK_DIR; + + private String env; + + private String argsLine; + + private ByteArrayOutputStream stdout = new ByteArrayOutputStream(); + + private ByteArrayOutputStream stderr = new ByteArrayOutputStream(); + + private InputStream stdin = new InteractiveInputStream(); + + private Throwable err; + + private Thread stdoutRunner; + + private Thread stderrRunner; + + public AbstractExec(String workDir, String argsLine, InputStream stdin) { + this(workDir, argsLine, null, stdin); + } + + public AbstractExec(String workDir, String argsLine, String env, InputStream stdin) { + if (workDir != null) { + this.workDir = workDir; + } + + this.argsLine = argsLine; + this.env = env; + + if (stdin != null) { + this.stdin = stdin; + } + } + + public abstract String getCmd(); + + public void execute() { + executeAsync(); + if (err == null) { + waitCompletion(); + } + } + + + public void executeAsync() { + + try { + if (OS_ARCH.isWindows()) { + String cmd = (env != null ? "set " + env + " & " : "") + fixPath(getCmd()) + " " + fixQuotes(argsLine); + System.out.println("Executing: cmd.exe /c " + cmd); + process = Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", cmd}, null, new File(workDir)); + } else { + String cmd = (env != null ? env + " " : "") + getCmd() + " " + argsLine; + System.out.println("Executing: sh -c " + cmd); + process = Runtime.getRuntime().exec(new String[]{"sh", "-c", cmd}, null, new File(workDir)); + } + + stdoutRunner = new StreamReaderThread(process.getInputStream(), logStreams ? new LoggingOutputStream("STDOUT", stdout) : stdout); + stdoutRunner.start(); + + stderrRunner = new StreamReaderThread(process.getErrorStream(), logStreams ? new LoggingOutputStream("STDERR", stderr) : stderr); + stderrRunner.start(); + + new StreamReaderThread(stdin, process.getOutputStream()) + .start(); + + } catch (Throwable t) { + err = t; + } + } + + private String fixPath(String cmd) { + return cmd.replaceAll("/", "\\\\"); + } + + private String fixQuotes(String argsLine) { + argsLine = argsLine + " "; + argsLine = argsLine.replaceAll("\"", "\\\\\""); + argsLine = argsLine.replaceAll(" '", " \""); + argsLine = argsLine.replaceAll("' ", "\" "); + return argsLine; + } + + public void waitCompletion() { + + // This is necessary to make sure the process isn't stuck reading from stdin + if (stdin instanceof InteractiveInputStream) { + ((InteractiveInputStream) stdin).close(); + } + try { + if (process.waitFor(waitTimeout, TimeUnit.MILLISECONDS)) { + exitCode = process.exitValue(); + if (exitCode != 0) { + dumpStreams = true; + } + // make sure reading output is really done (just in case) + stdoutRunner.join(5000); + stderrRunner.join(5000); + } else { + if (process.isAlive()) { + process.destroyForcibly(); + } + throw new RuntimeException("Timeout after " + (waitTimeout / 1000) + " seconds."); + } + } catch (InterruptedException e) { + dumpStreams = true; + throw new RuntimeException("Interrupted ...", e); + } catch (Throwable t) { + dumpStreams = true; + err = t; + } finally { + if (!logStreams && dumpStreams) try { + System.out.println("STDOUT: "); + copyStream(new ByteArrayInputStream(stdout.toByteArray()), System.out); + System.out.println("STDERR: "); + copyStream(new ByteArrayInputStream(stderr.toByteArray()), System.out); + } catch (Exception ignored) { + } + } + } + + public int exitCode() { + return exitCode; + } + + public Throwable error() { + return err; + } + + public InputStream stdout() { + return new ByteArrayInputStream(stdout.toByteArray()); + } + + public List stdoutLines() { + return parseStreamAsLines(new ByteArrayInputStream(stdout.toByteArray())); + } + + public String stdoutString() { + return new String(stdout.toByteArray()); + } + + public InputStream stderr() { + return new ByteArrayInputStream(stderr.toByteArray()); + } + + public List stderrLines() { + return parseStreamAsLines(new ByteArrayInputStream(stderr.toByteArray())); + } + + public String stderrString() { + return new String(stderr.toByteArray()); + } + + static List parseStreamAsLines(InputStream stream) { + List lines = new ArrayList<>(); + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); + + String line; + while ((line = reader.readLine()) != null) { + lines.add(line); + } + return lines; + } catch (IOException e) { + throw new RuntimeException("Unexpected I/O error", e); + } + } + + public void waitForStdout(String content) { + long start = System.currentTimeMillis(); + while (System.currentTimeMillis() - start < waitTimeout) { + if (stdoutString().indexOf(content) != -1) { + return; + } + try { + Thread.sleep(10); + } catch (InterruptedException e) { + throw new RuntimeException("Interrupted ...", e); + } + } + + throw new RuntimeException("Timed while waiting for content to appear in stdout"); + } + + public void sendToStdin(String s) { + if (stdin instanceof InteractiveInputStream) { + ((InteractiveInputStream) stdin).pushBytes(s.getBytes()); + } else { + throw new RuntimeException("Can't push to stdin - not interactive"); + } + } + + + + static void copyStream(InputStream is, OutputStream os) throws IOException { + byte [] buf = new byte[8192]; + + try (InputStream iss = is) { + int c; + while ((c = iss.read(buf)) != -1) { + os.write(buf, 0, c); + os.flush(); + } + } + } + + +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/AbstractExecBuilder.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/AbstractExecBuilder.java new file mode 100644 index 0000000000..97ec7724b4 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/AbstractExecBuilder.java @@ -0,0 +1,44 @@ +package org.keycloak.testsuite.cli.exec; + +import java.io.InputStream; + +/** + * @author Marko Strukelj + */ +public abstract class AbstractExecBuilder { + + protected String workDir; + protected String argsLine; + protected InputStream stdin; + protected String env; + protected boolean dumpStreams; + + public AbstractExecBuilder workDir(String path) { + this.workDir = path; + return this; + } + + public AbstractExecBuilder argsLine(String cmd) { + this.argsLine = cmd; + return this; + } + + public AbstractExecBuilder stdin(InputStream is) { + this.stdin = is; + return this; + } + + public AbstractExecBuilder env(String env) { + this.env = env; + return this; + } + + public AbstractExecBuilder fullStreamDump() { + this.dumpStreams = true; + return this; + } + + public abstract T execute(); + + public abstract T executeAsync(); +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/ExecutionException.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/ExecutionException.java similarity index 93% rename from testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/ExecutionException.java rename to testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/ExecutionException.java index 986f48627a..f5e053c998 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/ExecutionException.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/ExecutionException.java @@ -1,4 +1,4 @@ -package org.keycloak.testsuite.cli; +package org.keycloak.testsuite.cli.exec; /** * @author Marko Strukelj diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/InteractiveInputStream.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/InteractiveInputStream.java new file mode 100644 index 0000000000..cbbea7242b --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/InteractiveInputStream.java @@ -0,0 +1,120 @@ +package org.keycloak.testsuite.cli.exec; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InterruptedIOException; +import java.util.LinkedList; + +class InteractiveInputStream extends InputStream { + + private LinkedList queue = new LinkedList<>(); + + private Thread consumer; + + private boolean closed; + + @Override + public int read(byte b[]) throws IOException { + return read(b, 0, b.length); + } + + @Override + public synchronized int read(byte[] b, int off, int len) throws IOException { + + Byte current = null; + int rc = 0; + try { + consumer = Thread.currentThread(); + + do { + current = queue.poll(); + if (current == null) { + if (rc > 0) { + return rc; + } else { + do { + if (closed) { + return -1; + } + wait(); + } + while ((current = queue.poll()) == null); + } + } + + b[off + rc] = current; + rc++; + } while (rc < len); + + } catch (InterruptedException e) { + throw new InterruptedIOException("Signalled to exit"); + } finally { + consumer = null; + } + return rc; + } + + @Override + public long skip(long n) throws IOException { + return super.skip(n); + } + + @Override + public int available() throws IOException { + return super.available(); + } + + @Override + public synchronized void mark(int readlimit) { + super.mark(readlimit); + } + + @Override + public synchronized void reset() throws IOException { + super.reset(); + } + + @Override + public boolean markSupported() { + return super.markSupported(); + } + + @Override + public synchronized int read() throws IOException { + // when input is available pass it on + Byte current; + try { + consumer = Thread.currentThread(); + + while ((current = queue.poll()) == null) { + // we don't check for closed before making sure + // that there is nothing more to read + if (closed) { + return -1; + } + wait(); + } + + } catch (InterruptedException e) { + throw new InterruptedIOException("Signalled to exit"); + } finally { + consumer = null; + } + return current; + } + + @Override + public synchronized void close() { + closed = true; + if (consumer != null) { + consumer.interrupt(); + } + } + + public synchronized void pushBytes(byte [] buff) { + for (byte b : buff) { + queue.add(b); + } + notify(); + } +} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/LoggingOutputStream.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/LoggingOutputStream.java new file mode 100644 index 0000000000..e13fbe3487 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/LoggingOutputStream.java @@ -0,0 +1,53 @@ +package org.keycloak.testsuite.cli.exec; + +import java.io.ByteArrayOutputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +class LoggingOutputStream extends FilterOutputStream { + + private ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + private String name; + + public LoggingOutputStream(String name, OutputStream os) { + super(os); + this.name = name; + } + + @Override + public void write(int b) throws IOException { + super.write(b); + if (b == 10) { + log(); + } else { + buffer.write(b); + } + } + + @Override + public void write(byte[] buf) throws IOException { + write(buf, 0, buf.length); + } + + @Override + public void write(byte[] buf, int offs, int len) throws IOException { + for (int i = 0; i < len; i++) { + write(buf[offs+i]); + } + } + + @Override + public void close() throws IOException { + super.close(); + if (buffer.size() > 0) { + log(); + } + } + + private void log() { + String log = new String(buffer.toByteArray()); + buffer.reset(); + System.out.println("[" + name + "] " + log); + } +} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/NullInputStream.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/NullInputStream.java new file mode 100644 index 0000000000..cb34314158 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/NullInputStream.java @@ -0,0 +1,12 @@ +package org.keycloak.testsuite.cli.exec; + +import java.io.IOException; +import java.io.InputStream; + +class NullInputStream extends InputStream { + + @Override + public int read() throws IOException { + return -1; + } +} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/StreamReaderThread.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/StreamReaderThread.java new file mode 100644 index 0000000000..c7518d5b56 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/cli/exec/StreamReaderThread.java @@ -0,0 +1,33 @@ +package org.keycloak.testsuite.cli.exec; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static org.keycloak.testsuite.cli.exec.AbstractExec.copyStream; + +class StreamReaderThread extends Thread { + + private InputStream is; + private OutputStream os; + + StreamReaderThread(InputStream is, OutputStream os) { + this.is = is; + this.os = os; + } + + public void run() { + try { + copyStream(is, os); + } catch (IOException e) { + throw new RuntimeException("Unexpected I/O error", e); + } finally { + try { + os.close(); + } catch (IOException ignored) { + System.err.print("IGNORED: error while closing output stream: "); + ignored.printStackTrace(); + } + } + } +} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java index 68a5836721..bed2d328ed 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java @@ -18,6 +18,7 @@ package org.keycloak.testsuite.client.resources; import org.jboss.resteasy.annotations.cache.NoCache; +import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.representations.idm.AdminEventRepresentation; import org.keycloak.representations.idm.AuthenticationFlowRepresentation; import org.keycloak.representations.idm.EventRepresentation; @@ -251,4 +252,8 @@ public interface TestingResource { @Produces(MediaType.APPLICATION_JSON) Map getIdentityProviderConfig(@QueryParam("alias") String alias); + @GET + @Path("/component") + @Produces(MediaType.APPLICATION_JSON) + MultivaluedHashMap getComponentConfig(@QueryParam("componentId") String componentId); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java index b9afd9bfce..de17221a11 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountTest.java @@ -22,9 +22,11 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; + import org.keycloak.events.Details; import org.keycloak.events.Errors; import org.keycloak.events.EventType; +import org.keycloak.models.PasswordPolicy; import org.keycloak.models.utils.TimeBasedOTP; import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.RealmRepresentation; @@ -50,6 +52,7 @@ import org.keycloak.testsuite.util.IdentityProviderBuilder; import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.RealmBuilder; import org.keycloak.testsuite.util.UserBuilder; + import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; @@ -363,33 +366,90 @@ public class AccountTest extends TestRealmKeycloakTest { events.expectAccount(EventType.UPDATE_PASSWORD).assertEvent(); } - @Test - public void changePasswordWithPasswordHistoryPolicy() { - setPasswordPolicy("passwordHistory(2)"); + private void assertChangePasswordSucceeds(String currentPassword, String newPassword) { + changePasswordPage.changePassword(currentPassword, newPassword, newPassword); + Assert.assertEquals("Your password has been updated.", profilePage.getSuccess()); + events.expectAccount(EventType.UPDATE_PASSWORD).assertEvent(); + } + + private void assertChangePasswordFails(String currentPassword, String newPassword) { + changePasswordPage.changePassword(currentPassword, newPassword, newPassword); + Assert.assertThat(profilePage.getError(), containsString("Invalid password: must not be equal to any of last")); + events.expectAccount(EventType.UPDATE_PASSWORD_ERROR).error(Errors.PASSWORD_REJECTED).assertEvent(); + } + + @Test + public void changePasswordWithPasswordHistoryPolicyThreePasswords() { + setPasswordPolicy(PasswordPolicy.PASSWORD_HISTORY_ID + "(3)"); changePasswordPage.open(); loginPage.login("test-user@localhost", "password"); events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT + "?path=password").assertEvent(); - changePasswordPage.changePassword("password", "password", "password"); - Assert.assertEquals("Invalid password: must not be equal to any of last 2 passwords.", profilePage.getError()); - events.expectAccount(EventType.UPDATE_PASSWORD_ERROR).error(Errors.PASSWORD_REJECTED).assertEvent(); + assertChangePasswordFails ("password", "password"); // current: password + assertChangePasswordSucceeds("password", "password1"); // current: password - changePasswordPage.changePassword("password", "password1", "password1"); - Assert.assertEquals("Your password has been updated.", profilePage.getSuccess()); - events.expectAccount(EventType.UPDATE_PASSWORD).assertEvent(); + assertChangePasswordFails ("password1", "password"); // current: password1, history: password + assertChangePasswordFails ("password1", "password1"); // current: password1, history: password + assertChangePasswordSucceeds("password1", "password2"); // current: password1, history: password - changePasswordPage.changePassword("password1", "password", "password"); - Assert.assertEquals("Invalid password: must not be equal to any of last 2 passwords.", profilePage.getError()); - events.expectAccount(EventType.UPDATE_PASSWORD_ERROR).error(Errors.PASSWORD_REJECTED).assertEvent(); + assertChangePasswordFails ("password2", "password"); // current: password2, history: password, password1 + assertChangePasswordFails ("password2", "password1"); // current: password2, history: password, password1 + assertChangePasswordFails ("password2", "password2"); // current: password2, history: password, password1 + assertChangePasswordSucceeds("password2", "password3"); // current: password2, history: password, password1 - changePasswordPage.changePassword("password1", "password1", "password1"); - Assert.assertEquals("Invalid password: must not be equal to any of last 2 passwords.", profilePage.getError()); - events.expectAccount(EventType.UPDATE_PASSWORD_ERROR).error(Errors.PASSWORD_REJECTED).assertEvent(); + assertChangePasswordSucceeds("password3", "password"); // current: password3, history: password1, password2 + } - changePasswordPage.changePassword("password1", "password2", "password2"); - Assert.assertEquals("Your password has been updated.", profilePage.getSuccess()); - events.expectAccount(EventType.UPDATE_PASSWORD).assertEvent(); + @Test + public void changePasswordWithPasswordHistoryPolicyTwoPasswords() { + setPasswordPolicy(PasswordPolicy.PASSWORD_HISTORY_ID + "(2)"); + + changePasswordPage.open(); + loginPage.login("test-user@localhost", "password"); + events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT + "?path=password").assertEvent(); + + assertChangePasswordFails ("password", "password"); // current: password + assertChangePasswordSucceeds("password", "password1"); // current: password + + assertChangePasswordFails ("password1", "password"); // current: password1, history: password + assertChangePasswordFails ("password1", "password1"); // current: password1, history: password + assertChangePasswordSucceeds("password1", "password2"); // current: password1, history: password + + assertChangePasswordFails ("password2", "password1"); // current: password2, history: password1 + assertChangePasswordFails ("password2", "password2"); // current: password2, history: password1 + assertChangePasswordSucceeds("password2", "password"); // current: password2, history: password1 + } + + @Test + public void changePasswordWithPasswordHistoryPolicyOnePwds() { + // One password means only the active password is checked + setPasswordPolicy(PasswordPolicy.PASSWORD_HISTORY_ID + "(1)"); + + changePasswordPage.open(); + loginPage.login("test-user@localhost", "password"); + events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT + "?path=password").assertEvent(); + + assertChangePasswordFails ("password", "password"); // current: password + assertChangePasswordSucceeds("password", "password1"); // current: password + + assertChangePasswordFails ("password1", "password1"); // current: password1 + assertChangePasswordSucceeds("password1", "password"); // current: password1 + } + + @Test + public void changePasswordWithPasswordHistoryPolicyZeroPwdsInHistory() { + setPasswordPolicy(PasswordPolicy.PASSWORD_HISTORY_ID + "(0)"); + + changePasswordPage.open(); + loginPage.login("test-user@localhost", "password"); + events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT + "?path=password").assertEvent(); + + assertChangePasswordFails ("password", "password"); // current: password + assertChangePasswordSucceeds("password", "password1"); // current: password + + assertChangePasswordFails ("password1", "password1"); // current: password1 + assertChangePasswordSucceeds("password1", "password"); // current: password1 } @Test @@ -489,6 +549,12 @@ public class AccountTest extends TestRealmKeycloakTest { testRealm().update(testRealm); } + private void setDuplicateEmailsAllowed(boolean allowed) { + RealmRepresentation testRealm = testRealm().toRepresentation(); + testRealm.setDuplicateEmailsAllowed(allowed); + testRealm().update(testRealm); + } + @Test public void changeUsername() { // allow to edit the username in realm @@ -599,7 +665,7 @@ public class AccountTest extends TestRealmKeycloakTest { // KEYCLOAK-1534 @Test - public void changeEmailToExisting() { + public void changeEmailToExistingForbidden() { profilePage.open(); loginPage.login("test-user@localhost", "password"); @@ -633,6 +699,24 @@ public class AccountTest extends TestRealmKeycloakTest { profilePage.updateProfile("Tom", "Brady", "test-user@localhost"); events.expectAccount(EventType.UPDATE_PROFILE).assertEvent(); } + + @Test + public void changeEmailToExistingAllowed() { + setDuplicateEmailsAllowed(true); + + profilePage.open(); + loginPage.login("test-user@localhost", "password"); + + events.expectLogin().client("account").detail(Details.REDIRECT_URI, ACCOUNT_REDIRECT).assertEvent(); + + Assert.assertEquals("test-user@localhost", profilePage.getUsername()); + Assert.assertEquals("test-user@localhost", profilePage.getEmail()); + + // Change to the email, which some other user has + profilePage.updateProfile("New first", "New last", "test-user-no-access@localhost"); + + Assert.assertEquals("Your account has been updated.", profilePage.getSuccess()); + } @Test public void setupTotp() { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java index e68af584be..7aa31607c2 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java @@ -264,7 +264,7 @@ public class RequiredActionEmailVerificationTest extends TestRealmKeycloakTest { .clearDetails() .assertEvent(); - String badKeyURL = KeycloakUriBuilder.fromUri(resendEmailLink).queryParam("key", "foo").build().toString(); + String badKeyURL = KeycloakUriBuilder.fromUri(resendEmailLink).replaceQueryParam("key", "foo").build().toString(); driver.navigate().to(badKeyURL); events.expectRequiredAction(EventType.VERIFY_EMAIL_ERROR) @@ -276,6 +276,37 @@ public class RequiredActionEmailVerificationTest extends TestRealmKeycloakTest { .assertEvent(); } + @Test + public void verifyEmailBadCode() throws IOException, MessagingException { + loginPage.open(); + loginPage.login("test-user@localhost", "password"); + + Assert.assertTrue(verifyEmailPage.isCurrent()); + + Assert.assertEquals(1, greenMail.getReceivedMessages().length); + + MimeMessage message = greenMail.getReceivedMessages()[0]; + + String verificationUrl = getPasswordResetEmailLink(message); + + verificationUrl = KeycloakUriBuilder.fromUri(verificationUrl).replaceQueryParam("code", "foo").build().toString(); + + events.poll(); + + driver.navigate().to(verificationUrl.trim()); + + assertEquals("The link you clicked is a old stale link and is no longer valid. Maybe you have already verified your email?", errorPage.getError()); + + events.expectRequiredAction(EventType.VERIFY_EMAIL_ERROR) + .error(Errors.INVALID_CODE) + .client((String)null) + .user((String)null) + .session((String)null) + .clearDetails() + .assertEvent(); + } + + public static String getPasswordResetEmailLink(MimeMessage message) throws IOException, MessagingException { Multipart multipart = (Multipart) message.getContent(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java index 55eccaad5c..d6f913487e 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/AbstractPhotozExampleAdapterTest.java @@ -609,6 +609,27 @@ public abstract class AbstractPhotozExampleAdapterTest extends AbstractExampleAd this.deployer.undeploy(RESOURCE_SERVER_ID); } } + + //KEYCLOAK-3777 + @Test + public void testEntitlementRequest() { + try { + this.deployer.deploy(RESOURCE_SERVER_ID); + + clientPage.navigateTo(); + loginToClientPage("admin", "admin"); + + clientPage.requestEntitlements(); + assertTrue(driver.getPageSource().contains("urn:photoz.com:scopes:album:admin:manage")); + + clientPage.requestEntitlement(); + String pageSource = driver.getPageSource(); + assertTrue(pageSource.contains("urn:photoz.com:scopes:album:view")); + assertFalse(pageSource.contains("urn:photoz.com:scopes:album:admin:manage")); + } finally { + this.deployer.undeploy(RESOURCE_SERVER_ID); + } + } private void importResourceServerSettings() throws FileNotFoundException { getAuthorizationResource().importSettings(loadJson(new FileInputStream(new File(TEST_APPS_HOME_DIR + "/photoz/photoz-restful-api-authz-service.json")), ResourceServerRepresentation.class)); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractOIDCPublicKeyRotationAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractOIDCPublicKeyRotationAdapterTest.java index c1d5b577ba..fc6bd67b25 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractOIDCPublicKeyRotationAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractOIDCPublicKeyRotationAdapterTest.java @@ -19,6 +19,7 @@ package org.keycloak.testsuite.adapter.servlet; import java.io.IOException; import java.io.InputStream; +import java.util.Map; import java.util.concurrent.TimeUnit; import javax.ws.rs.core.Response; @@ -41,6 +42,7 @@ import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.StreamUtil; import org.keycloak.common.util.Time; import org.keycloak.constants.AdapterConstants; +import org.keycloak.jose.jws.AlgorithmType; import org.keycloak.keys.KeyProvider; import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; import org.keycloak.protocol.oidc.OIDCLoginProtocolService; @@ -48,6 +50,7 @@ import org.keycloak.representations.AccessToken; import org.keycloak.representations.adapters.action.GlobalRequestResult; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ComponentRepresentation; +import org.keycloak.representations.idm.KeysMetadataRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest; import org.keycloak.testsuite.adapter.filter.AdapterActionsFilter; @@ -58,9 +61,7 @@ import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.util.URLAssert; import org.openqa.selenium.By; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO; import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlEquals; import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith; @@ -201,9 +202,9 @@ public abstract class AbstractOIDCPublicKeyRotationAdapterTest extends AbstractS Assert.assertEquals(200, status); // Re-generate realm public key and remove the old key - String oldKeyId = getActiveKeyId(); + String oldActiveKeyProviderId = getActiveKeyProvider(); generateNewRealmKey(); - adminClient.realm(DEMO).components().component(oldKeyId).remove(); + adminClient.realm(DEMO).components().component(oldActiveKeyProviderId).remove(); // Send REST request to the customer-db app. Should be still succcessfully authenticated as the JWKPublicKeyLocator cache is still valid status = invokeRESTEndpoint(accessTokenString); @@ -237,7 +238,8 @@ public abstract class AbstractOIDCPublicKeyRotationAdapterTest extends AbstractS String accessTokenString = tokenMinTTLPage.getAccessTokenString(); // Generate new realm public key - String oldKeyId = getActiveKeyId(); + String oldActiveKeyProviderId = getActiveKeyProvider(); + generateNewRealmKey(); // Send REST request to customer-db app. It should be successfully authenticated even that token is signed by the old key @@ -245,7 +247,7 @@ public abstract class AbstractOIDCPublicKeyRotationAdapterTest extends AbstractS Assert.assertEquals(200, status); // Remove the old realm key now - adminClient.realm(DEMO).components().component(oldKeyId).remove(); + adminClient.realm(DEMO).components().component(oldActiveKeyProviderId).remove(); // Set some offset to ensure pushing notBefore will pass setAdapterAndServerTimeOffset(130, customerDb.toString() + "/unsecured/foo", tokenMinTTLPage.toString() + "/unsecured/foo"); @@ -295,13 +297,17 @@ public abstract class AbstractOIDCPublicKeyRotationAdapterTest extends AbstractS response.close(); } - private String getActiveKeyId() { - String realmId = adminClient.realm(DEMO).toRepresentation().getId(); - return adminClient.realm(DEMO).components().query(realmId, KeyProvider.class.getName()) - .get(0).getId(); + private String getActiveKeyProvider() { + KeysMetadataRepresentation keyMetadata = adminClient.realm(DEMO).keys().getKeyMetadata(); + String activeKid = keyMetadata.getActive().get(AlgorithmType.RSA.name()); + for (KeysMetadataRepresentation.KeyMetadataRepresentation rep : keyMetadata.getKeys()) { + if (rep.getKid().equals(activeKid)) { + return rep.getProviderId(); + } + } + return null; } - private int invokeRESTEndpoint(String accessTokenString) { HttpClient client = new DefaultHttpClient(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java index 5daec87c20..52ea7f7412 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java @@ -30,7 +30,8 @@ import org.keycloak.common.util.KeyUtils; import org.keycloak.common.util.PemUtils; import org.keycloak.keys.Attributes; import org.keycloak.keys.KeyProvider; -import org.keycloak.keys.RsaKeyProviderFactory; +import org.keycloak.keys.ImportedRsaKeyProviderFactory; +import org.keycloak.protocol.saml.SamlConfigAttributes; import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.protocol.saml.mappers.AttributeStatementHelper; import org.keycloak.protocol.saml.mappers.RoleListMapper; @@ -42,6 +43,7 @@ import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.saml.BaseSAML2BindingBuilder; import org.keycloak.saml.SAML2ErrorResponseBuilder; import org.keycloak.saml.common.constants.JBossSAMLURIConstants; +import org.keycloak.saml.common.util.XmlKeyInfoKeyNameTransformer; import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest; import org.keycloak.testsuite.adapter.page.BadAssertionSalesPostSig; import org.keycloak.testsuite.adapter.page.BadClientSalesPostSigServlet; @@ -71,6 +73,7 @@ import org.keycloak.testsuite.auth.page.login.Login; import org.keycloak.testsuite.auth.page.login.SAMLIDPInitiatedLogin; import org.keycloak.testsuite.page.AbstractPage; import org.keycloak.testsuite.util.IOUtil; +import org.keycloak.testsuite.util.RealmBuilder; import org.keycloak.testsuite.util.UserBuilder; import org.openqa.selenium.By; @@ -439,20 +442,21 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd testSuccessfulAndUnauthorizedLogin(employeeSigServletPage, testRealmSAMLRedirectLoginPage); } + private static final KeyPair NEW_KEY_PAIR = KeyUtils.generateRsaKeyPair(1024); + private static final String NEW_KEY_PRIVATE_KEY_PEM = PemUtils.encodeKey(NEW_KEY_PAIR.getPrivate()); + private PublicKey createKeys(String priority) throws Exception { - KeyPair keyPair = KeyUtils.generateRsaKeyPair(1024); - String privateKeyPem = PemUtils.encodeKey(keyPair.getPrivate()); - PublicKey publicKey = keyPair.getPublic(); + PublicKey publicKey = NEW_KEY_PAIR.getPublic(); ComponentRepresentation rep = new ComponentRepresentation(); rep.setName("mycomponent"); rep.setParentId("demo"); - rep.setProviderId(RsaKeyProviderFactory.ID); + rep.setProviderId(ImportedRsaKeyProviderFactory.ID); rep.setProviderType(KeyProvider.class.getName()); org.keycloak.common.util.MultivaluedHashMap config = new org.keycloak.common.util.MultivaluedHashMap(); config.addFirst("priority", priority); - config.addFirst(Attributes.PRIVATE_KEY_KEY, privateKeyPem); + config.addFirst(Attributes.PRIVATE_KEY_KEY, NEW_KEY_PRIVATE_KEY_PEM); rep.setConfig(config); testRealmResource().components().add(rep); @@ -492,11 +496,53 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd testRotatedKeysPropagated(employeeSigPostNoIdpKeyServletPage, testRealmSAMLPostLoginPage); } + @Test + public void employeeSigPostNoIdpKeyTestNoKeyNameInKeyInfo() throws Exception { + RealmRepresentation r = testRealmResource().toRepresentation(); + r.getAttributes().put(SamlConfigAttributes.SAML_SERVER_SIGNATURE_KEYINFO_KEY_NAME_TRANSFORMER, XmlKeyInfoKeyNameTransformer.NONE.name()); + testRotatedKeysPropagated(employeeSigPostNoIdpKeyServletPage, testRealmSAMLPostLoginPage); + } + + @Test + public void employeeSigPostNoIdpKeyTestCertSubjectAsKeyNameInKeyInfo() throws Exception { + RealmRepresentation r = testRealmResource().toRepresentation(); + r.getAttributes().put(SamlConfigAttributes.SAML_SERVER_SIGNATURE_KEYINFO_KEY_NAME_TRANSFORMER, XmlKeyInfoKeyNameTransformer.CERT_SUBJECT.name()); + testRotatedKeysPropagated(employeeSigPostNoIdpKeyServletPage, testRealmSAMLPostLoginPage); + } + + @Test + public void employeeSigPostNoIdpKeyTestKeyIdAsKeyNameInKeyInfo() throws Exception { + RealmRepresentation r = testRealmResource().toRepresentation(); + r.getAttributes().put(SamlConfigAttributes.SAML_SERVER_SIGNATURE_KEYINFO_KEY_NAME_TRANSFORMER, XmlKeyInfoKeyNameTransformer.KEY_ID.name()); + testRotatedKeysPropagated(employeeSigPostNoIdpKeyServletPage, testRealmSAMLPostLoginPage); + } + @Test public void employeeSigRedirNoIdpKeyTest() throws Exception { testRotatedKeysPropagated(employeeSigRedirNoIdpKeyServletPage, testRealmSAMLRedirectLoginPage); } + @Test + public void employeeSigRedirNoIdpKeyTestNoKeyNameInKeyInfo() throws Exception { + RealmRepresentation r = testRealmResource().toRepresentation(); + r.getAttributes().put(SamlConfigAttributes.SAML_SERVER_SIGNATURE_KEYINFO_KEY_NAME_TRANSFORMER, XmlKeyInfoKeyNameTransformer.NONE.name()); + testRotatedKeysPropagated(employeeSigRedirNoIdpKeyServletPage, testRealmSAMLRedirectLoginPage); + } + + @Test + public void employeeSigRedirNoIdpKeyTestCertSubjectAsKeyNameInKeyInfo() throws Exception { + RealmRepresentation r = testRealmResource().toRepresentation(); + r.getAttributes().put(SamlConfigAttributes.SAML_SERVER_SIGNATURE_KEYINFO_KEY_NAME_TRANSFORMER, XmlKeyInfoKeyNameTransformer.CERT_SUBJECT.name()); + testRotatedKeysPropagated(employeeSigRedirNoIdpKeyServletPage, testRealmSAMLRedirectLoginPage); + } + + @Test + public void employeeSigRedirNoIdpKeyTestKeyIdAsKeyNameInKeyInfo() throws Exception { + RealmRepresentation r = testRealmResource().toRepresentation(); + r.getAttributes().put(SamlConfigAttributes.SAML_SERVER_SIGNATURE_KEYINFO_KEY_NAME_TRANSFORMER, XmlKeyInfoKeyNameTransformer.KEY_ID.name()); + testRotatedKeysPropagated(employeeSigRedirNoIdpKeyServletPage, testRealmSAMLRedirectLoginPage); + } + @Test public void employeeSigRedirOptNoIdpKeyTest() throws Exception { testRotatedKeysPropagated(employeeSigRedirOptNoIdpKeyServletPage, testRealmSAMLRedirectLoginPage); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java index b23194a7ac..18277d27ec 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/PermissionsTest.java @@ -1173,7 +1173,9 @@ public class PermissionsTest extends AbstractKeycloakTest { }, Resource.USER, false); invoke(new InvocationWithResponse() { public void invoke(RealmResource realm, AtomicReference response) { - response.set(realm.groups().add(new GroupRepresentation())); + GroupRepresentation group = new GroupRepresentation(); + group.setName("mygroup"); + response.set(realm.groups().add(group)); } }, Resource.USER, true); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java index 18c1616f12..fc8b25bbe9 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java @@ -19,6 +19,7 @@ package org.keycloak.testsuite.admin.authentication; import org.junit.Test; import org.keycloak.authentication.authenticators.broker.IdpCreateUserIfUniqueAuthenticatorFactory; +import org.keycloak.common.Profile; import org.keycloak.representations.idm.AuthenticatorConfigInfoRepresentation; import org.keycloak.representations.idm.ConfigPropertyRepresentation; import org.keycloak.testsuite.Assert; @@ -32,8 +33,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import static org.keycloak.common.Profile.isPreviewEnabled; - /** * @author Marko Strukelj */ @@ -137,7 +136,7 @@ public class ProvidersTest extends AbstractAuthenticationTest { "Validates a OTP on a separate OTP form. Only shown if required based on the configured conditions."); addProviderInfo(result, "auth-cookie", "Cookie", "Validates the SSO cookie set by the auth server."); addProviderInfo(result, "auth-otp-form", "OTP Form", "Validates a OTP on a separate OTP form."); - if (isPreviewEnabled()) { + if (Profile.isFeatureEnabled(Profile.Feature.SCRIPTS)) { addProviderInfo(result, "auth-script-based", "Script", "Script based authentication. Allows to define custom authentication logic via JavaScript."); } addProviderInfo(result, "auth-spnego", "Kerberos", "Initiates the SPNEGO protocol. Most often used with Kerberos."); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java index 02a2cdb3d4..eed784ba9f 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java @@ -84,12 +84,32 @@ public abstract class AbstractClientTest extends AbstractAuthTest { } protected String createOidcClient(String name) { + return createClient(createOidcClientRep(name)); + } + + protected String createOidcBearerOnlyClient(String name) { + ClientRepresentation clientRep = createOidcClientRep(name); + clientRep.setBearerOnly(Boolean.TRUE); + clientRep.setPublicClient(Boolean.FALSE); + return createClient(clientRep); + } + + protected String createOidcBearerOnlyClientWithAuthz(String name) { + ClientRepresentation clientRep = createOidcClientRep(name); + clientRep.setBearerOnly(Boolean.TRUE); + clientRep.setPublicClient(Boolean.FALSE); + clientRep.setAuthorizationServicesEnabled(Boolean.TRUE); + clientRep.setServiceAccountsEnabled(Boolean.TRUE); + return createClient(clientRep); + } + + protected ClientRepresentation createOidcClientRep(String name) { ClientRepresentation clientRep = new ClientRepresentation(); clientRep.setClientId(name); clientRep.setName(name); clientRep.setRootUrl("foo"); - clientRep.setProtocol("openid-connect"); - return createClient(clientRep); + clientRep.setProtocol("openid-connect"); + return clientRep; } protected String createSamlClient(String name) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/ClientDescriptionConverterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/ClientDescriptionConverterTest.java new file mode 100644 index 0000000000..2718d3a8ca --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/ClientDescriptionConverterTest.java @@ -0,0 +1,41 @@ +/* + * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package org.keycloak.testsuite.admin.client; + +import org.keycloak.testsuite.AbstractAuthTest; + +import java.io.IOException; +import java.io.InputStream; +import org.apache.commons.io.IOUtils; +import org.junit.Test; + +/** + * + * @author hmlnarik + */ +public class ClientDescriptionConverterTest extends AbstractAuthTest { + + // https://issues.jboss.org/browse/KEYCLOAK-4040 + @Test + public void testOrganizationDetailsMetadata() throws IOException { + try (InputStream is = ClientDescriptionConverterTest.class.getResourceAsStream("KEYCLOAK-4040-sharefile-metadata.xml")) { + String data = IOUtils.toString(is, "UTF-8"); + testRealmResource().convertClientDescription(data); + } + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java index 4328c8f999..a33b2a11b2 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/InstallationTest.java @@ -35,17 +35,28 @@ import static org.hamcrest.Matchers.*; public class InstallationTest extends AbstractClientTest { private static final String OIDC_NAME = "oidcInstallationClient"; + private static final String OIDC_NAME_BEARER_ONLY_NAME = "oidcInstallationClientBearerOnly"; + private static final String OIDC_NAME_BEARER_ONLY_WITH_AUTHZ_NAME = "oidcInstallationClientBearerOnlyWithAuthz"; private static final String SAML_NAME = "samlInstallationClient"; private ClientResource oidcClient; private String oidcClientId; + private ClientResource oidcBearerOnlyClient; + private String oidcBearerOnlyClientId; + private ClientResource oidcBearerOnlyClientWithAuthz; + private String oidcBearerOnlyClientWithAuthzId; private ClientResource samlClient; private String samlClientId; @Before public void createClients() { oidcClientId = createOidcClient(OIDC_NAME); + oidcBearerOnlyClientId = createOidcBearerOnlyClient(OIDC_NAME_BEARER_ONLY_NAME); + oidcBearerOnlyClientWithAuthzId = createOidcBearerOnlyClientWithAuthz(OIDC_NAME_BEARER_ONLY_WITH_AUTHZ_NAME); + oidcClient = findClientResource(OIDC_NAME); + oidcBearerOnlyClient = findClientResource(OIDC_NAME_BEARER_ONLY_NAME); + oidcBearerOnlyClientWithAuthz = findClientResource(OIDC_NAME_BEARER_ONLY_WITH_AUTHZ_NAME); samlClientId = createSamlClient(SAML_NAME); samlClient = findClientResource(SAML_NAME); @@ -54,6 +65,8 @@ public class InstallationTest extends AbstractClientTest { @After public void tearDown() { removeClient(oidcClientId); + removeClient(oidcBearerOnlyClientId); + removeClient(oidcBearerOnlyClientWithAuthzId); removeClient(samlClientId); } @@ -78,6 +91,25 @@ public class InstallationTest extends AbstractClientTest { assertOidcInstallationConfig(json); } + @Test + public void testOidcBearerOnlyJson() { + String json = oidcBearerOnlyClient.getInstallationProvider("keycloak-oidc-keycloak-json"); + assertOidcInstallationConfig(json); + assertThat(json, containsString("bearer-only")); + assertThat(json, not(containsString("public-client"))); + assertThat(json, not(containsString("credentials"))); + } + + @Test + public void testOidcBearerOnlyWithAuthzJson() { + String json = oidcBearerOnlyClientWithAuthz.getInstallationProvider("keycloak-oidc-keycloak-json"); + assertOidcInstallationConfig(json); + assertThat(json, containsString("bearer-only")); + assertThat(json, not(containsString("public-client"))); + assertThat(json, containsString("credentials")); + assertThat(json, containsString("secret")); + } + private void assertOidcInstallationConfig(String config) { assertThat(config, containsString("master")); assertThat(config, not(containsString(ApiUtil.findActiveKey(testRealmResource()).getPublicKey()))); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/EnforcerConfigTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/EnforcerConfigTest.java index f1e407f9f0..081bb79305 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/EnforcerConfigTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/EnforcerConfigTest.java @@ -27,6 +27,7 @@ import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.ProfileAssume; import java.util.List; +import java.util.Map; import static org.junit.Assert.assertEquals; import static org.keycloak.testsuite.util.IOUtil.loadRealm; @@ -49,8 +50,8 @@ public class EnforcerConfigTest extends AbstractKeycloakTest { public void testMultiplePathsWithSameName() throws Exception{ KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/authorization-test/enforcer-config-paths-same-name.json")); PolicyEnforcer policyEnforcer = deployment.getPolicyEnforcer(); - List paths = policyEnforcer.getPaths(); + Map paths = policyEnforcer.getPaths(); assertEquals(1, paths.size()); - assertEquals(4, paths.get(0).getMethods().size()); + assertEquals(4, paths.values().iterator().next().getMethods().size()); } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/GenericPolicyManagementTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/GenericPolicyManagementTest.java index e6f83d8af0..a1a2bfc628 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/GenericPolicyManagementTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/authorization/GenericPolicyManagementTest.java @@ -38,6 +38,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; @@ -71,7 +72,7 @@ public class GenericPolicyManagementTest extends AbstractAuthorizationTest { PolicyRepresentation newPolicy = createTestingPolicy().toRepresentation(); assertEquals("Test Generic Policy", newPolicy.getName()); - assertEquals("test", newPolicy.getType()); + assertEquals("scope", newPolicy.getType()); assertEquals(Logic.POSITIVE, newPolicy.getLogic()); assertEquals(DecisionStrategy.UNANIMOUS, newPolicy.getDecisionStrategy()); assertEquals("configuration for A", newPolicy.getConfig().get("configA")); @@ -98,28 +99,28 @@ public class GenericPolicyManagementTest extends AbstractAuthorizationTest { @Test public void testUpdate() { PolicyResource policyResource = createTestingPolicy(); - PolicyRepresentation resource = policyResource.toRepresentation(); + PolicyRepresentation policy = policyResource.toRepresentation(); - resource.setName("changed"); - resource.setLogic(Logic.NEGATIVE); - resource.setDecisionStrategy(DecisionStrategy.AFFIRMATIVE); - resource.getConfig().put("configA", "changed configuration for A"); - resource.getConfig().remove("configB"); - resource.getConfig().put("configC", "changed configuration for C"); + policy.setName("changed"); + policy.setLogic(Logic.NEGATIVE); + policy.setDecisionStrategy(DecisionStrategy.AFFIRMATIVE); + policy.getConfig().put("configA", "changed configuration for A"); + policy.getConfig().remove("configB"); + policy.getConfig().put("configC", "changed configuration for C"); - policyResource.update(resource); + policyResource.update(policy); - resource = policyResource.toRepresentation(); + policy = policyResource.toRepresentation(); - assertEquals("changed", resource.getName()); - assertEquals(Logic.NEGATIVE, resource.getLogic()); + assertEquals("changed", policy.getName()); + assertEquals(Logic.NEGATIVE, policy.getLogic()); - assertEquals(DecisionStrategy.AFFIRMATIVE, resource.getDecisionStrategy()); - assertEquals("changed configuration for A", resource.getConfig().get("configA")); - assertNull(resource.getConfig().get("configB")); - assertEquals("changed configuration for C", resource.getConfig().get("configC")); + assertEquals(DecisionStrategy.AFFIRMATIVE, policy.getDecisionStrategy()); + assertEquals("changed configuration for A", policy.getConfig().get("configA")); + assertNull(policy.getConfig().get("configB")); + assertEquals("changed configuration for C", policy.getConfig().get("configC")); - Map config = resource.getConfig(); + Map config = policy.getConfig(); config.put("applyPolicies", buildConfigOption(findPolicyByName("Test Associated C").getId())); @@ -127,22 +128,25 @@ public class GenericPolicyManagementTest extends AbstractAuthorizationTest { config.put("scopes", buildConfigOption(findScopeByName("Test Scope A").getId())); - policyResource.update(resource); + policyResource.update(policy); - resource = policyResource.toRepresentation(); - config = resource.getConfig(); + policy = policyResource.toRepresentation(); + config = policy.getConfig(); - assertAssociatedPolicy("Test Associated C", resource); - assertFalse(config.get("applyPolicies").contains(findPolicyByName("Test Associated A").getId())); - assertFalse(config.get("applyPolicies").contains(findPolicyByName("Test Associated B").getId())); + assertAssociatedPolicy("Test Associated C", policy); + List associatedPolicies = getClientResource().authorization().policies().policy(policy.getId()).associatedPolicies(); + assertFalse(associatedPolicies.stream().filter(associated -> associated.getId().equals(findPolicyByName("Test Associated A").getId())).findFirst().isPresent()); + assertFalse(associatedPolicies.stream().filter(associated -> associated.getId().equals(findPolicyByName("Test Associated B").getId())).findFirst().isPresent()); - assertAssociatedResource("Test Resource B", resource); - assertFalse(config.get("resources").contains(findResourceByName("Test Resource A").getId())); - assertFalse(config.get("resources").contains(findResourceByName("Test Resource C").getId())); + assertAssociatedResource("Test Resource B", policy); + List resources = policyResource.resources(); + assertFalse(resources.contains(findResourceByName("Test Resource A"))); + assertFalse(resources.contains(findResourceByName("Test Resource C"))); - assertAssociatedScope("Test Scope A", resource); - assertFalse(config.get("scopes").contains(findScopeByName("Test Scope B").getId())); - assertFalse(config.get("scopes").contains(findScopeByName("Test Scope C").getId())); + assertAssociatedScope("Test Scope A", policy); + List scopes = getClientResource().authorization().policies().policy(policy.getId()).scopes(); + assertFalse(scopes.contains(findScopeByName("Test Scope B").getId())); + assertFalse(scopes.contains(findScopeByName("Test Scope C").getId())); } @Test @@ -186,7 +190,7 @@ public class GenericPolicyManagementTest extends AbstractAuthorizationTest { PolicyRepresentation newPolicy = new PolicyRepresentation(); newPolicy.setName(name); - newPolicy.setType("test"); + newPolicy.setType("scope"); newPolicy.setConfig(config); PoliciesResource policies = getClientResource().authorization().policies(); @@ -264,27 +268,38 @@ public class GenericPolicyManagementTest extends AbstractAuthorizationTest { private void assertAssociatedPolicy(String associatedPolicyName, PolicyRepresentation dependentPolicy) { PolicyRepresentation associatedPolicy = findPolicyByName(associatedPolicyName); + PoliciesResource policies = getClientResource().authorization().policies(); + associatedPolicy = policies.policy(associatedPolicy.getId()).toRepresentation(); assertNotNull(associatedPolicy); - assertTrue(dependentPolicy.getConfig().get("applyPolicies").contains(associatedPolicy.getId())); - assertEquals(1, associatedPolicy.getDependentPolicies().size()); - assertEquals(dependentPolicy.getId(), associatedPolicy.getDependentPolicies().get(0).getId()); + PolicyRepresentation finalAssociatedPolicy = associatedPolicy; + PolicyResource policyResource = policies.policy(dependentPolicy.getId()); + List associatedPolicies = policyResource.associatedPolicies(); + assertTrue(associatedPolicies.stream().filter(associated -> associated.getId().equals(finalAssociatedPolicy.getId())).findFirst().isPresent()); + List dependentPolicies = policies.policy(associatedPolicy.getId()).dependentPolicies(); + assertEquals(1, dependentPolicies.size()); + assertEquals(dependentPolicy.getId(), dependentPolicies.get(0).getId()); } private void assertAssociatedResource(String resourceName, PolicyRepresentation policy) { ResourceRepresentation resource = findResourceByName(resourceName); assertNotNull(resource); - assertTrue(policy.getConfig().get("resources").contains(resource.getId())); - assertEquals(1, resource.getPolicies().size()); - assertTrue(resource.getPolicies().stream().map(PolicyRepresentation::getId).collect(Collectors.toList()) + List resources = getClientResource().authorization().policies().policy(policy.getId()).resources(); + assertTrue(resources.contains(resource)); + List policies = getClientResource().authorization().resources().resource(resource.getId()).permissions(); + assertEquals(1, policies.size()); + assertTrue(policies.stream().map(PolicyRepresentation::getId).collect(Collectors.toList()) .contains(policy.getId())); } private void assertAssociatedScope(String scopeName, PolicyRepresentation policy) { ScopeRepresentation scope = findScopeByName(scopeName); + scope = getClientResource().authorization().scopes().scope(scope.getId()).toRepresentation(); assertNotNull(scope); - assertTrue(policy.getConfig().get("scopes").contains(scope.getId())); - assertEquals(1, scope.getPolicies().size()); - assertTrue(scope.getPolicies().stream().map(PolicyRepresentation::getId).collect(Collectors.toList()) + List scopes = getClientResource().authorization().policies().policy(policy.getId()).scopes(); + assertTrue(scopes.stream().map((Function) rep -> rep.getId()).collect(Collectors.toList()).contains(scope.getId())); + List permissions = getClientResource().authorization().scopes().scope(scope.getId()).permissions(); + assertEquals(1, permissions.size()); + assertTrue(permissions.stream().map(PolicyRepresentation::getId).collect(Collectors.toList()) .contains(policy.getId())); } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupMappersTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupMappersTest.java index a1e6b0ad36..b1338e9e6d 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupMappersTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupMappersTest.java @@ -34,9 +34,6 @@ import org.keycloak.representations.idm.UserRepresentation; import java.util.*; -import static org.hamcrest.CoreMatchers.hasItems; -import static org.hamcrest.MatcherAssert.assertThat; - /** * @author Marko Strukelj */ @@ -119,7 +116,7 @@ public class GroupMappersTest extends AbstractGroupTest { Assert.assertNotNull(groups); Assert.assertTrue(groups.size() == 1); Assert.assertEquals("topGroup", groups.get(0)); - Assert.assertEquals(Collections.singletonList("true"), token.getOtherClaims().get("topAttribute")); + Assert.assertEquals("true", token.getOtherClaims().get("topAttribute")); } { UserRepresentation user = realm.users().search("level2GroupUser", -1, -1).get(0); @@ -132,8 +129,8 @@ public class GroupMappersTest extends AbstractGroupTest { Assert.assertNotNull(groups); Assert.assertTrue(groups.size() == 1); Assert.assertEquals("level2group", groups.get(0)); - Assert.assertEquals(Collections.singletonList("true"), token.getOtherClaims().get("topAttribute")); - Assert.assertEquals(Collections.singletonList("true"), token.getOtherClaims().get("level2Attribute")); + Assert.assertEquals("true", token.getOtherClaims().get("topAttribute")); + Assert.assertEquals("true", token.getOtherClaims().get("level2Attribute")); } } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java index 91d299f138..79908d8e69 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java @@ -151,6 +151,32 @@ public class GroupTest extends AbstractGroupTest { return group; } + @Test + public void doNotAllowSameGroupNameAtSameLevel() throws Exception { + RealmResource realm = adminClient.realms().realm("test"); + + GroupRepresentation topGroup = new GroupRepresentation(); + topGroup.setName("top"); + topGroup = createGroup(realm, topGroup); + + GroupRepresentation anotherTopGroup = new GroupRepresentation(); + anotherTopGroup.setName("top"); + Response response = realm.groups().add(anotherTopGroup); + assertEquals(409, response.getStatus()); // conflict status 409 - same name not allowed + + GroupRepresentation level2Group = new GroupRepresentation(); + level2Group.setName("level2"); + response = realm.groups().group(topGroup.getId()).subGroup(level2Group); + response.close(); + assertEquals(201, response.getStatus()); // created status + + GroupRepresentation anotherlevel2Group = new GroupRepresentation(); + anotherlevel2Group.setName("level2"); + response = realm.groups().group(topGroup.getId()).subGroup(anotherlevel2Group); + response.close(); + assertEquals(409, response.getStatus()); // conflict status 409 - same name not allowed + } + @Test public void createAndTestGroups() throws Exception { RealmResource realm = adminClient.realms().realm("test"); @@ -179,7 +205,7 @@ public class GroupTest extends AbstractGroupTest { GroupRepresentation topGroup = new GroupRepresentation(); topGroup.setName("top"); topGroup = createGroup(realm, topGroup); - + List roles = new LinkedList<>(); roles.add(topRole); realm.groups().group(topGroup.getId()).roles().realmLevel().add(roles); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialimport/PartialImportTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialimport/PartialImportTest.java index 783adbfaf2..e6f73480a9 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialimport/PartialImportTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialimport/PartialImportTest.java @@ -56,6 +56,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER; /** * Tests for the partial import endpoint in admin client. Also tests the @@ -86,6 +87,7 @@ public class PartialImportTest extends AbstractAuthTest { public void initAdminEvents() { RealmRepresentation realmRep = RealmBuilder.edit(testRealmResource().toRepresentation()).testEventListener().build(); realmId = realmRep.getId(); + realmRep.setDuplicateEmailsAllowed(false); adminClient.realm(realmRep.getRealm()).update(realmRep); piRep = new PartialImportRepresentation(); @@ -320,6 +322,40 @@ public class PartialImportTest extends AbstractAuthTest { } } + @Test + public void testAddUsersWithDuplicateEmailsForbidden() { + assertAdminEvents.clear(); + + setFail(); + addUsers(); + + UserRepresentation user = createUserRepresentation(USER_PREFIX + 999, USER_PREFIX + 1 + "@foo.com", "foo", "bar", true); + piRep.getUsers().add(user); + + Response response = testRealmResource().partialImport(piRep); + assertEquals(409, response.getStatus()); + } + + @Test + public void testAddUsersWithDuplicateEmailsAllowed() { + + RealmRepresentation realmRep = new RealmRepresentation(); + realmRep.setDuplicateEmailsAllowed(true); + adminClient.realm(realmId).update(realmRep); + + assertAdminEvents.clear(); + + setFail(); + addUsers(); + doImport(); + + UserRepresentation user = createUserRepresentation(USER_PREFIX + 999, USER_PREFIX + 1 + "@foo.com", "foo", "bar", true); + piRep.setUsers(Arrays.asList(user)); + + PartialImportResults results = doImport(); + assertEquals(1, results.getAdded()); + } + @Test public void testAddUsersWithTermsAndConditions() { assertAdminEvents.clear(); @@ -590,4 +626,23 @@ public class PartialImportTest extends AbstractAuthTest { assertEquals(NUM_ENTITIES * NUM_RESOURCE_TYPES, results.getOverwritten()); } + //KEYCLOAK-3042 + @Test + public void testOverwriteExistingClientWithRoles() { + setOverwrite(); + + ClientRepresentation client = adminClient.realm(MASTER).clients().findByClientId("broker").get(0); + List clientRoles = adminClient.realm(MASTER).clients().get(client.getId()).roles().list(); + + Map> clients = new HashMap<>(); + clients.put(client.getClientId(), clientRoles); + + RolesRepresentation roles = new RolesRepresentation(); + roles.setClient(clients); + + piRep.setClients(Arrays.asList(client)); + piRep.setRoles(roles); + + doImport(); + } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java index 26cf87b153..d0a8bde8f6 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/realm/RealmTest.java @@ -412,6 +412,8 @@ public class RealmTest extends AbstractAdminTest { if (realm.isRegistrationEmailAsUsername() != null) assertEquals(realm.isRegistrationEmailAsUsername(), storedRealm.isRegistrationEmailAsUsername()); if (realm.isRememberMe() != null) assertEquals(realm.isRememberMe(), storedRealm.isRememberMe()); if (realm.isVerifyEmail() != null) assertEquals(realm.isVerifyEmail(), storedRealm.isVerifyEmail()); + if (realm.isLoginWithEmailAllowed() != null) assertEquals(realm.isLoginWithEmailAllowed(), storedRealm.isLoginWithEmailAllowed()); + if (realm.isDuplicateEmailsAllowed() != null) assertEquals(realm.isDuplicateEmailsAllowed(), storedRealm.isDuplicateEmailsAllowed()); if (realm.isResetPasswordAllowed() != null) assertEquals(realm.isResetPasswordAllowed(), storedRealm.isResetPasswordAllowed()); if (realm.isEditUsernameAllowed() != null) assertEquals(realm.isEditUsernameAllowed(), storedRealm.isEditUsernameAllowed()); if (realm.getSslRequired() != null) assertEquals(realm.getSslRequired(), storedRealm.getSslRequired()); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerConfiguration.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerConfiguration.java index 1fdb896275..aa3e56efe3 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerConfiguration.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerConfiguration.java @@ -87,7 +87,7 @@ public class KcOidcBrokerConfiguration implements BrokerConfiguration { userAttrMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); userAttrMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true"); userAttrMapperConfig.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true"); -// userAttrMapperConfig.put(ProtocolMapperUtils.MULTIVALUED, "true"); + userAttrMapperConfig.put(ProtocolMapperUtils.MULTIVALUED, "true"); client.setProtocolMappers(Arrays.asList(emailMapper, userAttrMapper)); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/AbstractCliTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/AbstractCliTest.java new file mode 100644 index 0000000000..8df18ae021 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/AbstractCliTest.java @@ -0,0 +1,54 @@ +package org.keycloak.testsuite.cli; + +import org.junit.Assert; +import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.cli.exec.AbstractExec; + +import java.util.List; + +/** + * @author Marko Strukelj + */ +public abstract class AbstractCliTest extends AbstractKeycloakTest { + + + public void assertExitCodeAndStdOutSize(AbstractExec exe, int exitCode, int stdOutLineCount) { + assertExitCodeAndStreamSizes(exe, exitCode, stdOutLineCount, -1); + } + + public void assertExitCodeAndStdErrSize(AbstractExec exe, int exitCode, int stdErrLineCount) { + assertExitCodeAndStreamSizes(exe, exitCode, -1, stdErrLineCount); + } + + public void assertExitCodeAndStreamSizes(AbstractExec exe, int exitCode, int stdOutLineCount, int stdErrLineCount) { + Assert.assertEquals("exitCode == " + exitCode, exitCode, exe.exitCode()); + if (stdOutLineCount != -1) { + try { + assertLineCount("stdout output", exe.stdoutLines(), stdOutLineCount); + } catch (Throwable e) { + throw new AssertionError("STDOUT: " + exe.stdoutString(), e); + } + } + if (stdErrLineCount != -1) { + try { + assertLineCount("stderr output", exe.stderrLines(), stdErrLineCount); + } catch (Throwable e) { + throw new AssertionError("STDERR: " + exe.stderrString(), e); + } + } + } + + private void assertLineCount(String label, List lines, int count) { + if (lines.size() == count) { + return; + } + // there is some kind of race condition in 'kcreg' that results in intermittent extra empty line + if (lines.size() == count + 1) { + if ("".equals(lines.get(lines.size()-1))) { + return; + } + } + Assert.assertTrue(label + " has " + lines.size() + " lines (expected: " + count + ")", lines.size() == count); + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/AbstractAdmCliTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/AbstractAdmCliTest.java new file mode 100644 index 0000000000..d86ddf1a09 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/AbstractAdmCliTest.java @@ -0,0 +1,387 @@ +package org.keycloak.testsuite.cli.admin; + +import org.junit.Assert; +import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator; +import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator; +import org.keycloak.client.admin.cli.config.ConfigData; +import org.keycloak.client.admin.cli.config.FileConfigHandler; +import org.keycloak.client.admin.cli.config.RealmConfigData; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.testsuite.cli.AbstractCliTest; +import org.keycloak.testsuite.cli.KcAdmExec; +import org.keycloak.testsuite.util.ClientBuilder; +import org.keycloak.testsuite.util.UserBuilder; +import org.keycloak.util.JsonSerialization; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson; +import static org.keycloak.testsuite.cli.KcAdmExec.WORK_DIR; +import static org.keycloak.testsuite.cli.KcAdmExec.execute; + +/** + * @author Marko Strukelj + */ +public abstract class AbstractAdmCliTest extends AbstractCliTest { + + protected String serverUrl = isAuthServerSSL() ? + "https://localhost:" + getAuthServerHttpsPort() + "/auth" : + "http://localhost:" + getAuthServerHttpPort() + "/auth"; + + static boolean runIntermittentlyFailingTests() { + return "true".equals(System.getProperty("test.intermittent")); + } + + static boolean isAuthServerSSL() { + return "true".equals(System.getProperty("auth.server.ssl.required")); + } + + static File getDefaultConfigFilePath() { + return new File(System.getProperty("user.home") + "/.keycloak/kcadm.config"); + } + + static int getAuthServerHttpsPort() { + try { + return Integer.valueOf(System.getProperty("auth.server.https.port")); + } catch (Exception e) { + throw new RuntimeException("System property 'auth.server.https.port' not set or invalid: '" + + System.getProperty("auth.server.https.port") + "'"); + } + } + + static int getAuthServerHttpPort() { + try { + return Integer.valueOf(System.getProperty("auth.server.http.port")); + } catch (Exception e) { + throw new RuntimeException("System property 'auth.server.http.port' not set or invalid: '" + + System.getProperty("auth.server.http.port") + "'"); + } + } + + @Override + public void addTestRealms(List testRealms) { + + RealmRepresentation realmRepresentation = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class); + testRealms.add(realmRepresentation); + + // create admin user account with permissions to manage clients + UserRepresentation admin = UserBuilder.create() + .username("user1") + .password("userpass") + .enabled(true) + .build(); + HashMap> clientRoles = new HashMap<>(); + clientRoles.put("realm-management", Arrays.asList("realm-admin")); + admin.setClientRoles(clientRoles); + realmRepresentation.getUsers().add(admin); + + + + // create client with service account to use Signed JWT credentials with + ClientRepresentation regClient = ClientBuilder.create() + .clientId("admin-cli-jwt") + .attribute(JWTClientAuthenticator.CERTIFICATE_ATTR, "MIICnTCCAYUCBgFXUhpRTTANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdyZWctY2xpMB4XDTE2MDkyMjEzMzIxOFoXDTI2MDkyMjEzMzM1OFowEjEQMA4GA1UEAwwHcmVnLWNsaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMHZn/0Bk1M9oKcTHxzn2cGvBWwO1m6OVLQ8LSVwNIf4ixfGkVIkhI5iEGYND+uD8ame54ZPClTVxMra3JldClLIG+L+ymnbT2vKIhEsVvCROs9PnYxbFALt1dXneLIio2uzF+d7/zQWlmeaWfNunSJT1aHNJDkGgDeUuQa25b0IMqsFjsN8Dg4ATkA97r3wKn4Tp3SE7sTM/B2pmra4atNxGeShVrgihqUiQ/PwDiDGwry64AsexkZnQsCR3bJWBAVUiHef3JWzTfWWN5bfCBG6Mnq1xw7YN+YpV1nR3CGmcKJuLe6aTe7Ps8hYejYiQA7Mp7ZQsoImsVFV5HDOlb0CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAZl8XvLfKXTPYvq/QyHOg7EDlAdlV3HkmHP9SBAV4BccmHmorMkm5I6I21UA5mfju+0nhbEd0bm0kvJFxIfNU6lJyyVvQx3Gns37KYUOzIV/ocWZuOTBLp5tfIBYbBwfE/s1J4PhpA/3WhBY9JKiLvdJfxECGIgaLs2M0UsylW/7o04+18Od8j/m7crQc7fpe5gJB5m/+hxUDowIjG5CumffX9OHYGDvHBpaUl7QNSGgjP8Bn9ogmIMUBJ7XSYUcohKuk2Cnj6p+GlLuqHbOISUXLVjf0DxhCu6diVxvacKbgAZmyCIO1tGL/UVRxg9GOYdCiC9vHfPuZ8US+ZB0P9g==") + .authenticatorType(JWTClientAuthenticator.PROVIDER_ID) + .serviceAccount() + .build(); + + realmRepresentation.getClients().add(regClient); + + // create service account for client reg-cli with permissions to manage clients + addServiceAccount(realmRepresentation, "admin-cli-jwt"); + + + + // create client to use with user account - enable direct grants + regClient = ClientBuilder.create() + .clientId("admin-cli-jwt-direct") + .attribute(JWTClientAuthenticator.CERTIFICATE_ATTR, "MIICnTCCAYUCBgFXUhpRTTANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdyZWctY2xpMB4XDTE2MDkyMjEzMzIxOFoXDTI2MDkyMjEzMzM1OFowEjEQMA4GA1UEAwwHcmVnLWNsaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMHZn/0Bk1M9oKcTHxzn2cGvBWwO1m6OVLQ8LSVwNIf4ixfGkVIkhI5iEGYND+uD8ame54ZPClTVxMra3JldClLIG+L+ymnbT2vKIhEsVvCROs9PnYxbFALt1dXneLIio2uzF+d7/zQWlmeaWfNunSJT1aHNJDkGgDeUuQa25b0IMqsFjsN8Dg4ATkA97r3wKn4Tp3SE7sTM/B2pmra4atNxGeShVrgihqUiQ/PwDiDGwry64AsexkZnQsCR3bJWBAVUiHef3JWzTfWWN5bfCBG6Mnq1xw7YN+YpV1nR3CGmcKJuLe6aTe7Ps8hYejYiQA7Mp7ZQsoImsVFV5HDOlb0CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAZl8XvLfKXTPYvq/QyHOg7EDlAdlV3HkmHP9SBAV4BccmHmorMkm5I6I21UA5mfju+0nhbEd0bm0kvJFxIfNU6lJyyVvQx3Gns37KYUOzIV/ocWZuOTBLp5tfIBYbBwfE/s1J4PhpA/3WhBY9JKiLvdJfxECGIgaLs2M0UsylW/7o04+18Od8j/m7crQc7fpe5gJB5m/+hxUDowIjG5CumffX9OHYGDvHBpaUl7QNSGgjP8Bn9ogmIMUBJ7XSYUcohKuk2Cnj6p+GlLuqHbOISUXLVjf0DxhCu6diVxvacKbgAZmyCIO1tGL/UVRxg9GOYdCiC9vHfPuZ8US+ZB0P9g==") + .authenticatorType(JWTClientAuthenticator.PROVIDER_ID) + .directAccessGrants() + .build(); + + realmRepresentation.getClients().add(regClient); + + + + + // create client with service account to use client secret with + regClient = ClientBuilder.create() + .clientId("admin-cli-secret") + .secret("password") + .authenticatorType(ClientIdAndSecretAuthenticator.PROVIDER_ID) + .serviceAccount() + .build(); + + realmRepresentation.getClients().add(regClient); + + // create service account for client reg-cli with permissions to manage clients + addServiceAccount(realmRepresentation, "admin-cli-secret"); + + + + + // create client to use with user account - enable direct grants + regClient = ClientBuilder.create() + .clientId("admin-cli-secret-direct") + .secret("password") + .authenticatorType(ClientIdAndSecretAuthenticator.PROVIDER_ID) + .directAccessGrants() + .build(); + + realmRepresentation.getClients().add(regClient); + + } + + FileConfigHandler initCustomConfigFile() { + String filename = UUID.randomUUID().toString() + ".config"; + File cfgFile = new File(WORK_DIR + "/" + filename); + FileConfigHandler handler = new FileConfigHandler(); + handler.setConfigFile(cfgFile.getAbsolutePath()); + return handler; + } + + void assertFieldsEqualWithExclusions(ConfigData config1, ConfigData config2, String ... excluded) { + + HashSet exclusions = new HashSet<>(Arrays.asList(excluded)); + + if (!exclusions.contains("serverUrl")) { + Assert.assertEquals("serverUrl", config1.getServerUrl(), config2.getServerUrl()); + } + if (!exclusions.contains("realm")) { + Assert.assertEquals("realm", config1.getRealm(), config2.getRealm()); + } + if (!exclusions.contains("truststore")) { + Assert.assertEquals("truststore", config1.getTruststore(), config2.getTruststore()); + } + if (!exclusions.contains("endpoints")) { + Map> endp1 = config1.getEndpoints(); + Map> endp2 = config2.getEndpoints(); + + Iterator>> it1 = endp1.entrySet().iterator(); + Iterator>> it2 = endp2.entrySet().iterator(); + + while (it1.hasNext()) { + Map.Entry> ent1 = it1.next(); + Map.Entry> ent2 = it2.next(); + + String serverUrl = ent1.getKey(); + String endpskey = "endpoints." + serverUrl; + if (!exclusions.contains(endpskey)) { + Assert.assertEquals(endpskey, ent1.getKey(), ent2.getKey()); + + Map realms1 = ent1.getValue(); + Map realms2 = ent2.getValue(); + + Iterator> rit1 = realms1.entrySet().iterator(); + Iterator> rit2 = realms2.entrySet().iterator(); + + while (rit1.hasNext()) { + Map.Entry rent1 = rit1.next(); + Map.Entry rent2 = rit2.next(); + + String realm = rent1.getKey(); + String rkey = endpskey + "." + realm; + if (!exclusions.contains(endpskey)) { + Assert.assertEquals(rkey, rent1.getKey(), rent2.getKey()); + + RealmConfigData rdata1 = rent1.getValue(); + RealmConfigData rdata2 = rent2.getValue(); + + assertFieldsEqualWithExclusions(serverUrl, realm, rdata1, rdata2, excluded); + } + } + } + } + } + } + + void assertFieldsEqualWithExclusions(String server, String realm, RealmConfigData data1, RealmConfigData data2, String ... excluded) { + + HashSet exclusions = new HashSet<>(Arrays.asList(excluded)); + + String pfix = ""; + if (server != null || realm != null) { + pfix = "endpoints." + server + "." + realm + "."; + } + + String ekey = pfix + "serverUrl"; + if (!exclusions.contains(ekey)) { + Assert.assertEquals(ekey, data1.serverUrl(), data2.serverUrl()); + } + + ekey = pfix + "realm"; + if (!exclusions.contains(ekey)) { + Assert.assertEquals(ekey, data1.realm(), data2.realm()); + } + + ekey = pfix + "clientId"; + if (!exclusions.contains(ekey)) { + Assert.assertEquals(ekey, data1.getClientId(), data2.getClientId()); + } + + ekey = pfix + "token"; + if (!exclusions.contains(ekey)) { + Assert.assertEquals(ekey, data1.getToken(), data2.getToken()); + } + + ekey = pfix + "refreshToken"; + if (!exclusions.contains(ekey)) { + Assert.assertEquals(ekey, data1.getRefreshToken(), data2.getRefreshToken()); + } + + ekey = pfix + "expiresAt"; + if (!exclusions.contains(ekey)) { + Assert.assertEquals(ekey, data1.getExpiresAt(), data2.getExpiresAt()); + } + + ekey = pfix + "refreshExpiresAt"; + if (!exclusions.contains(ekey)) { + Assert.assertEquals(ekey, data1.getRefreshExpiresAt(), data2.getRefreshExpiresAt()); + } + + ekey = pfix + "secret"; + if (!exclusions.contains(ekey)) { + Assert.assertEquals(ekey, data1.getSecret(), data2.getSecret()); + } + + ekey = pfix + "signingToken"; + if (!exclusions.contains(ekey)) { + Assert.assertEquals(ekey, data1.getSigningToken(), data2.getSigningToken()); + } + + ekey = pfix + "sigExpiresAt"; + if (!exclusions.contains(ekey)) { + Assert.assertEquals(ekey, data1.getSigExpiresAt(), data2.getSigExpiresAt()); + } + } + + void testCRUDWithOnTheFlyAuth(String serverUrl, String credentials, String extraOptions, String loginMessage) throws IOException { + + File configFile = getDefaultConfigFilePath(); + long lastModified = configFile.exists() ? configFile.lastModified() : 0; + + // This test assumes it is the only user of any instance of on the system + KcAdmExec exe = execute("create clients --no-config --server " + serverUrl + + " --realm test " + credentials + " " + extraOptions + " -s clientId=test-client -o"); + + Assert.assertEquals("exitCode == 0", 0, exe.exitCode()); + Assert.assertEquals("login message", loginMessage, exe.stderrLines().get(0)); + + ClientRepresentation client = JsonSerialization.readValue(exe.stdout(), ClientRepresentation.class); + Assert.assertEquals("clientId", "test-client", client.getClientId()); + + long lastModified2 = configFile.exists() ? configFile.lastModified() : 0; + Assert.assertEquals("config file not modified", lastModified, lastModified2); + + + + + exe = execute("get clients/" + client.getId() + " --no-config --server " + serverUrl + " --realm test " + credentials + " " + extraOptions); + + assertExitCodeAndStdErrSize(exe, 0, 1); + + ClientRepresentation client2 = JsonSerialization.readValue(exe.stdout(), ClientRepresentation.class); + Assert.assertEquals("clientId", "test-client", client2.getClientId()); + + lastModified2 = configFile.exists() ? configFile.lastModified() : 0; + Assert.assertEquals("config file not modified", lastModified, lastModified2); + + + + + exe = execute("update clients/" + client.getId() + " --no-config --server " + serverUrl + " --realm test " + + credentials + " " + extraOptions + " -s enabled=false -o"); + + assertExitCodeAndStdErrSize(exe, 0, 1); + + ClientRepresentation client4 = JsonSerialization.readValue(exe.stdout(), ClientRepresentation.class); + Assert.assertEquals("clientId", "test-client", client4.getClientId()); + Assert.assertFalse("enabled", client4.isEnabled()); + + lastModified2 = configFile.exists() ? configFile.lastModified() : 0; + Assert.assertEquals("config file not modified", lastModified, lastModified2); + + + + + exe = execute("delete clients/" + client.getId() + " --no-config --server " + serverUrl + " --realm test " + credentials + " " + extraOptions); + + assertExitCodeAndStreamSizes(exe, 0, 0, 1); + + lastModified2 = configFile.exists() ? configFile.lastModified() : 0; + Assert.assertEquals("config file not modified", lastModified, lastModified2); + + + + + // subsequent delete should fail + exe = execute("delete clients/" + client.getId() + " --no-config --server " + serverUrl + " --realm test " + credentials + " " + extraOptions); + + assertExitCodeAndStreamSizes(exe, 1, 0, 2); + String resourceUri = serverUrl + "/admin/realms/test/clients/" + client.getId(); + Assert.assertEquals("error message", "Resource not found for url: " + resourceUri, exe.stderrLines().get(1)); + + lastModified2 = configFile.exists() ? configFile.lastModified() : 0; + Assert.assertEquals("config file not modified", lastModified, lastModified2); + } + + File initTempFile(String extension) throws IOException { + return initTempFile(extension, null); + } + + File initTempFile(String extension, String content) throws IOException { + String filename = UUID.randomUUID().toString() + extension; + File file = new File(KcAdmExec.WORK_DIR + "/" + filename); + if (content != null) { + OutputStream os = new FileOutputStream(file); + os.write(content.getBytes(Charset.forName("iso_8859_1"))); + os.close(); + } + return file; + } + + void addServiceAccount(RealmRepresentation realm, String clientId) { + + UserRepresentation account = UserBuilder.create() + .username("service-account-" + clientId) + .enabled(true) + .serviceAccountId(clientId) + .build(); + + HashMap> clientRoles = new HashMap<>(); + clientRoles.put("realm-management", Arrays.asList("realm-admin")); + + account.setClientRoles(clientRoles); + + realm.getUsers().add(account); + } + + void loginAsUser(File configFile, String server, String realm, String user, String password) { + + KcAdmExec exe = KcAdmExec.execute("config credentials --server " + server + " --realm " + realm + + " --user " + user + " --password " + password + " --config " + configFile.getAbsolutePath()); + + Assert.assertEquals("exitCode == 0", 0, exe.exitCode()); + + List lines = exe.stdoutLines(); + Assert.assertTrue("stdout output empty", lines.size() == 0); + + lines = exe.stderrLines(); + Assert.assertTrue("stderr output one line", lines.size() == 1); + Assert.assertEquals("stderr first line", "Logging into " + server + " as user " + user + " of realm " + realm, lines.get(0)); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmCreateTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmCreateTest.java new file mode 100644 index 0000000000..2481b58816 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmCreateTest.java @@ -0,0 +1,131 @@ +package org.keycloak.testsuite.cli.admin; + +import org.junit.Assert; +import org.junit.Test; +import org.keycloak.client.admin.cli.config.FileConfigHandler; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.testsuite.cli.KcAdmExec; +import org.keycloak.testsuite.util.TempFileResource; +import org.keycloak.util.JsonSerialization; + +import java.io.IOException; +import java.util.Arrays; + +import static org.keycloak.testsuite.cli.KcAdmExec.execute; + +/** + * @author Marko Strukelj + */ +public class KcAdmCreateTest extends AbstractAdmCliTest { + + @Test + public void testCreateWithRealmOverride() throws IOException { + + FileConfigHandler handler = initCustomConfigFile(); + + try (TempFileResource configFile = new TempFileResource(handler.getConfigFile())) { + + // authenticate as a regular user against one realm + KcAdmExec exe = execute("config credentials -x --config '" + configFile.getName() + + "' --server " + serverUrl + " --realm master --user admin --password admin"); + + assertExitCodeAndStreamSizes(exe, 0, 0, 1); + + exe = execute("create clients --config '" + configFile.getName() + "' --server " + serverUrl + " -r test -s clientId=my_first_client"); + + assertExitCodeAndStreamSizes(exe, 0, 0, 1); + } + } + + + @Test + public void testCreateThoroughly() throws IOException { + + FileConfigHandler handler = initCustomConfigFile(); + + try (TempFileResource configFile = new TempFileResource(handler.getConfigFile())) { + + final String realm = "test"; + + // authenticate as a regular user against one realm + KcAdmExec exe = KcAdmExec.execute("config credentials -x --config '" + configFile.getName() + + "' --server " + serverUrl + " --realm master --user admin --password admin"); + + assertExitCodeAndStreamSizes(exe, 0, 0, 1); + + // create configuration from file using stdin redirect ... output an object + String content = "{\n" + + " \"clientId\": \"my_client\",\n" + + " \"enabled\": true,\n" + + " \"redirectUris\": [\"http://localhost:8980/myapp/*\"],\n" + + " \"serviceAccountsEnabled\": true,\n" + + " \"name\": \"My Client App\",\n" + + " \"implicitFlowEnabled\": false,\n" + + " \"publicClient\": true,\n" + + " \"webOrigins\": [\"http://localhost:8980/myapp\"],\n" + + " \"consentRequired\": false,\n" + + " \"baseUrl\": \"http://localhost:8980/myapp\",\n" + + " \"bearerOnly\": true,\n" + + " \"standardFlowEnabled\": true\n" + + "}"; + + try (TempFileResource tmpFile = new TempFileResource(initTempFile(".json", content))) { + + exe = execute("create clients --config '" + configFile.getName() + "' -o -f - < '" + tmpFile.getName() + "'"); + + assertExitCodeAndStdErrSize(exe, 0, 0); + + ClientRepresentation client = JsonSerialization.readValue(exe.stdout(), ClientRepresentation.class); + Assert.assertNotNull("id", client.getId()); + Assert.assertEquals("clientId", "my_client", client.getClientId()); + Assert.assertEquals("enabled", true, client.isEnabled()); + Assert.assertEquals("redirectUris", Arrays.asList("http://localhost:8980/myapp/*"), client.getRedirectUris()); + Assert.assertEquals("serviceAccountsEnabled", true, client.isServiceAccountsEnabled()); + Assert.assertEquals("name", "My Client App", client.getName()); + Assert.assertEquals("implicitFlowEnabled", false, client.isImplicitFlowEnabled()); + Assert.assertEquals("publicClient", true, client.isPublicClient()); + // note there is no server-side check if protocol is supported + Assert.assertEquals("webOrigins", Arrays.asList("http://localhost:8980/myapp"), client.getWebOrigins()); + Assert.assertEquals("consentRequired", false, client.isConsentRequired()); + Assert.assertEquals("baseUrl", "http://localhost:8980/myapp", client.getBaseUrl()); + Assert.assertEquals("bearerOnly", true, client.isStandardFlowEnabled()); + Assert.assertFalse("mappers not empty", client.getProtocolMappers().isEmpty()); + + // create configuration from file as a template and override clientId and other attributes ... output an object + exe = execute("create clients --config '" + configFile.getName() + "' -o -f '" + tmpFile.getName() + + "' -s clientId=my_client2 -s enabled=false -s 'redirectUris=[\"http://localhost:8980/myapp2/*\"]'" + + " -s 'name=My Client App II' -s 'webOrigins=[\"http://localhost:8980/myapp2\"]'" + + " -s baseUrl=http://localhost:8980/myapp2 -s rootUrl=http://localhost:8980/myapp2"); + + assertExitCodeAndStdErrSize(exe, 0, 0); + + ClientRepresentation client2 = JsonSerialization.readValue(exe.stdout(), ClientRepresentation.class); + Assert.assertNotNull("id", client2.getId()); + Assert.assertEquals("clientId", "my_client2", client2.getClientId()); + Assert.assertEquals("enabled", false, client2.isEnabled()); + Assert.assertEquals("redirectUris", Arrays.asList("http://localhost:8980/myapp2/*"), client2.getRedirectUris()); + Assert.assertEquals("serviceAccountsEnabled", true, client2.isServiceAccountsEnabled()); + Assert.assertEquals("name", "My Client App II", client2.getName()); + Assert.assertEquals("implicitFlowEnabled", false, client2.isImplicitFlowEnabled()); + Assert.assertEquals("publicClient", true, client2.isPublicClient()); + Assert.assertEquals("webOrigins", Arrays.asList("http://localhost:8980/myapp2"), client2.getWebOrigins()); + Assert.assertEquals("consentRequired", false, client2.isConsentRequired()); + Assert.assertEquals("baseUrl", "http://localhost:8980/myapp2", client2.getBaseUrl()); + Assert.assertEquals("rootUrl", "http://localhost:8980/myapp2", client2.getRootUrl()); + Assert.assertEquals("bearerOnly", true, client2.isStandardFlowEnabled()); + Assert.assertFalse("mappers not empty", client2.getProtocolMappers().isEmpty()); + } + + // simple create, output an id + exe = execute("create clients --config '" + configFile.getName() + "' -i -s clientId=my_client3"); + + assertExitCodeAndStreamSizes(exe, 0, 1, 0); + + // simple create, default output + exe = execute("create clients --config '" + configFile.getName() + "' -s clientId=my_client4"); + + assertExitCodeAndStreamSizes(exe, 0, 0, 1); + Assert.assertTrue("only id returned", exe.stderrLines().get(0).startsWith("Created new client with id '")); + } + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmTest.java new file mode 100644 index 0000000000..fe2caa4b58 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmTest.java @@ -0,0 +1,561 @@ +package org.keycloak.testsuite.cli.admin; + +import org.junit.Assert; +import org.junit.Test; +import org.keycloak.client.admin.cli.config.ConfigData; +import org.keycloak.client.admin.cli.config.FileConfigHandler; +import org.keycloak.client.admin.cli.config.RealmConfigData; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.testsuite.cli.KcAdmExec; +import org.keycloak.testsuite.util.TempFileResource; +import org.keycloak.util.JsonSerialization; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.List; +import java.util.UUID; + +import static org.keycloak.client.admin.cli.util.OsUtil.CMD; +import static org.keycloak.client.admin.cli.util.OsUtil.EOL; +import static org.keycloak.testsuite.cli.KcAdmExec.execute; + +/** + * @author Marko Strukelj + */ +public class KcAdmTest extends AbstractAdmCliTest { + + @Test + public void testBadCommand() { + /* + * Test most basic execution with non-existent command + */ + KcAdmExec exe = execute("nonexistent"); + + assertExitCodeAndStreamSizes(exe, 1, 0, 1); + Assert.assertEquals("stderr first line", "Unknown command: nonexistent", exe.stderrLines().get(0)); + } + + + @Test + public void testNoArgs() { + /* + * Test (sub)commands without any arguments + */ + KcAdmExec exe = KcAdmExec.execute(""); + + assertExitCodeAndStdErrSize(exe, 1, 0); + + List lines = exe.stdoutLines(); + Assert.assertTrue("stdout output not empty", lines.size() > 0); + Assert.assertEquals("stdout first line", "Keycloak Admin CLI", lines.get(0)); + Assert.assertEquals("stdout one but last line", "Use '" + KcAdmExec.CMD + " help ' for more information about a given command.", lines.get(lines.size() - 2)); + Assert.assertEquals("stdout last line", "", lines.get(lines.size() - 1)); + + + /* + * Test commands without arguments + */ + exe = KcAdmExec.execute("config"); + assertExitCodeAndStreamSizes(exe, 1, 0, 1); + Assert.assertEquals("error message", + "Sub-command required by '" + CMD + " config' - one of: 'credentials', 'truststore'", + exe.stderrLines().get(0)); + + exe = KcAdmExec.execute("config credentials"); + assertExitCodeAndStdErrSize(exe, 1, 0); + Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10); + Assert.assertEquals("help message", "Usage: " + CMD + " config credentials --server SERVER_URL --realm REALM --user USER [--password PASSWORD] [ARGUMENTS]", exe.stdoutLines().get(0)); + + exe = KcAdmExec.execute("config truststore"); + assertExitCodeAndStdErrSize(exe, 1, 0); + Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10); + Assert.assertEquals("help message", "Usage: " + CMD + " config truststore [TRUSTSTORE | --delete] [--trustpass PASSWORD] [ARGUMENTS]", exe.stdoutLines().get(0)); + + exe = KcAdmExec.execute("create"); + assertExitCodeAndStdErrSize(exe, 1, 0); + Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10); + Assert.assertEquals("help message", "Usage: " + CMD + " create ENDPOINT_URI [ARGUMENTS]", exe.stdoutLines().get(0)); + //Assert.assertEquals("error message", "No file nor attribute values specified", exe.stderrLines().get(0)); + + exe = KcAdmExec.execute("get"); + assertExitCodeAndStdErrSize(exe, 1, 0); + Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10); + Assert.assertEquals("help message", "Usage: " + CMD + " get ENDPOINT_URI [ARGUMENTS]", exe.stdoutLines().get(0)); + //Assert.assertEquals("error message", "CLIENT not specified", exe.stderrLines().get(0)); + + exe = KcAdmExec.execute("update"); + assertExitCodeAndStdErrSize(exe, 1, 0); + Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10); + Assert.assertEquals("help message", "Usage: " + CMD + " update ENDPOINT_URI [ARGUMENTS]", exe.stdoutLines().get(0)); + //Assert.assertEquals("error message", "No file nor attribute values specified", exe.stderrLines().get(0)); + + exe = KcAdmExec.execute("delete"); + assertExitCodeAndStdErrSize(exe, 1, 0); + Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10); + Assert.assertEquals("help message", "Usage: " + CMD + " delete ENDPOINT_URI [ARGUMENTS]", exe.stdoutLines().get(0)); + //Assert.assertEquals("error message", "CLIENT not specified", exe.stderrLines().get(0)); + + //exe = KcAdmExec.execute("get-roles"); + //assertExitCodeAndStdErrSize(exe, 0, 0); + //try { + // JsonNode node = JsonSerialization.readValue(exe.stdout(), JsonNode.class); + // Assert.assertTrue("is JSON array", node.isArray()); + //} catch (IOException e) { + // throw new AssertionError("Response should be a JSON array", e); + //} + + //Assert.assertTrue("JSON message returned", exe.stdoutLines().size() > 10); + //Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10); + //Assert.assertEquals("help message", "Usage: " + CMD + " get-roles [--cclientid CLIENT_ID | --cid ID] [ARGUMENTS]", exe.stdoutLines().get(0)); + + exe = KcAdmExec.execute("add-roles"); + assertExitCodeAndStdErrSize(exe, 1, 0); + Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10); + Assert.assertEquals("help message", "Usage: " + CMD + " add-roles (--uusername USERNAME | --uid ID) [--cclientid CLIENT_ID | --cid ID] (--rolename NAME | --roleid ID)+ [ARGUMENTS]", exe.stdoutLines().get(0)); + //Assert.assertEquals("error message", "CLIENT not specified", exe.stderrLines().get(0)); + + exe = KcAdmExec.execute("remove-roles"); + assertExitCodeAndStdErrSize(exe, 1, 0); + Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10); + Assert.assertEquals("help message", "Usage: " + CMD + " remove-roles (--uusername USERNAME | --uid ID) [--cclientid CLIENT_ID | --cid ID] (--rolename NAME | --roleid ID)+ [ARGUMENTS]", exe.stdoutLines().get(0)); + + exe = KcAdmExec.execute("set-password"); + assertExitCodeAndStdErrSize(exe, 1, 0); + Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10); + Assert.assertEquals("help message", "Usage: " + CMD + " set-password (--username USERNAME | --userid ID) [--password PASSWORD] [ARGUMENTS]", exe.stdoutLines().get(0)); + //Assert.assertEquals("error message", "CLIENT not specified", exe.stderrLines().get(0)); + + exe = KcAdmExec.execute("help"); + assertExitCodeAndStdErrSize(exe, 0, 0); + lines = exe.stdoutLines(); + Assert.assertTrue("stdout output not empty", lines.size() > 0); + Assert.assertEquals("stdout first line", "Keycloak Admin CLI", lines.get(0)); + Assert.assertEquals("stdout one but last line", "Use '" + KcAdmExec.CMD + " help ' for more information about a given command.", lines.get(lines.size() - 2)); + Assert.assertEquals("stdout last line", "", lines.get(lines.size() - 1)); + } + + @Test + public void testHelpGlobalOption() { + /* + * Test --help for all commands + */ + KcAdmExec exe = KcAdmExec.execute("--help"); + assertExitCodeAndStdErrSize(exe, 0, 0); + Assert.assertEquals("stdout first line", "Keycloak Admin CLI", exe.stdoutLines().get(0)); + + exe = KcAdmExec.execute("create --help"); + assertExitCodeAndStdErrSize(exe, 0, 0); + Assert.assertEquals("stdout first line", "Usage: " + CMD + " create ENDPOINT_URI [ARGUMENTS]", exe.stdoutLines().get(0)); + + exe = KcAdmExec.execute("get --help"); + assertExitCodeAndStdErrSize(exe, 0, 0); + Assert.assertEquals("stdout first line", "Usage: " + CMD + " get ENDPOINT_URI [ARGUMENTS]", exe.stdoutLines().get(0)); + + exe = KcAdmExec.execute("update --help"); + assertExitCodeAndStdErrSize(exe, 0, 0); + Assert.assertEquals("stdout first line", "Usage: " + CMD + " update ENDPOINT_URI [ARGUMENTS]", exe.stdoutLines().get(0)); + + exe = KcAdmExec.execute("delete --help"); + assertExitCodeAndStdErrSize(exe, 0, 0); + Assert.assertEquals("stdout first line", "Usage: " + CMD + " delete ENDPOINT_URI [ARGUMENTS]", exe.stdoutLines().get(0)); + + exe = KcAdmExec.execute("get-roles --help"); + assertExitCodeAndStdErrSize(exe, 0, 0); + Assert.assertEquals("stdout first line", "Usage: " + CMD + " get-roles [--cclientid CLIENT_ID | --cid ID] [ARGUMENTS]", exe.stdoutLines().get(0)); + + exe = KcAdmExec.execute("add-roles --help"); + assertExitCodeAndStdErrSize(exe, 0, 0); + Assert.assertEquals("stdout first line", "Usage: " + CMD + " add-roles (--uusername USERNAME | --uid ID) [--cclientid CLIENT_ID | --cid ID] (--rolename NAME | --roleid ID)+ [ARGUMENTS]", exe.stdoutLines().get(0)); + + exe = KcAdmExec.execute("remove-roles --help"); + assertExitCodeAndStdErrSize(exe, 0, 0); + Assert.assertEquals("stdout first line", "Usage: " + CMD + " remove-roles (--uusername USERNAME | --uid ID) [--cclientid CLIENT_ID | --cid ID] (--rolename NAME | --roleid ID)+ [ARGUMENTS]", exe.stdoutLines().get(0)); + + exe = KcAdmExec.execute("set-password --help"); + assertExitCodeAndStdErrSize(exe, 0, 0); + Assert.assertEquals("stdout first line", "Usage: " + CMD + " set-password (--username USERNAME | --userid ID) [--password PASSWORD] [ARGUMENTS]", exe.stdoutLines().get(0)); + + exe = KcAdmExec.execute("config --help"); + assertExitCodeAndStdErrSize(exe, 0, 0); + Assert.assertEquals("stdout first line", "Usage: " + CMD + " config SUB_COMMAND [ARGUMENTS]", exe.stdoutLines().get(0)); + + exe = KcAdmExec.execute("config credentials --help"); + assertExitCodeAndStdErrSize(exe, 0, 0); + Assert.assertEquals("stdout first line", + "Usage: " + CMD + " config credentials --server SERVER_URL --realm REALM --user USER [--password PASSWORD] [ARGUMENTS]", + exe.stdoutLines().get(0)); + + exe = KcAdmExec.execute("config truststore --help"); + assertExitCodeAndStdErrSize(exe, 0, 0); + Assert.assertEquals("stdout first line", + "Usage: " + CMD + " config truststore [TRUSTSTORE | --delete] [--trustpass PASSWORD] [ARGUMENTS]", + exe.stdoutLines().get(0)); + } + + @Test + public void testBadOptionInPlaceOfCommand() { + /* + * Test most basic execution with non-existent option + */ + KcAdmExec exe = KcAdmExec.execute("--nonexistent"); + + assertExitCodeAndStreamSizes(exe, 1, 0, 1); + Assert.assertEquals("stderr first line", "Unknown command: --nonexistent", exe.stderrLines().get(0)); + } + + @Test + public void testBadOption() { + /* + * Test sub-command execution with non-existent option + */ + + KcAdmExec exe = KcAdmExec.execute("get users --nonexistent"); + + assertExitCodeAndStreamSizes(exe, 1, 0, 2); + Assert.assertEquals("stderr first line", "Invalid option: --nonexistent", exe.stderrLines().get(0)); + Assert.assertEquals("try help", "Try '" + CMD + " help get' for more information", exe.stderrLines().get(1)); + + // set-password doesn't use @Arguments injection thus unsupported options are handled by Aesh + exe = KcAdmExec.execute("set-password --nonexistent"); + + assertExitCodeAndStreamSizes(exe, 1, 0, 2); + Assert.assertEquals("stderr first line", "Invalid option: --nonexistent", exe.stderrLines().get(0)); + Assert.assertEquals("try help", "Try '" + CMD + " help set-password' for more information", exe.stderrLines().get(1)); + } + + @Test + public void testCredentialsServerAndRealmWithDefaultConfig() { + /* + * Test without --server specified + */ + KcAdmExec exe = KcAdmExec.execute("config credentials --server " + serverUrl + " --realm master"); + + assertExitCodeAndStreamSizes(exe, 0, 0, 0); + } + + @Test + public void testCredentialsNoServerWithDefaultConfig() { + /* + * Test without --server specified + */ + KcAdmExec exe = KcAdmExec.execute("config credentials --realm master --user admin --password admin"); + + assertExitCodeAndStreamSizes(exe, 1, 0, 2); + Assert.assertEquals("stderr first line", "Required option not specified: --server", exe.stderrLines().get(0)); + Assert.assertEquals("try help", "Try '" + CMD + " help config credentials' for more information", exe.stderrLines().get(1)); + } + + @Test + public void testCredentialsNoRealmWithDefaultConfig() { + /* + * Test without --server specified + */ + KcAdmExec exe = KcAdmExec.execute("config credentials --server " + serverUrl + " --user admin --password admin"); + + assertExitCodeAndStreamSizes(exe, 1, 0, 2); + Assert.assertEquals("stderr first line", "Required option not specified: --realm", exe.stderrLines().get(0)); + Assert.assertEquals("try help", "Try '" + CMD + " help config credentials' for more information", exe.stderrLines().get(1)); + } + + @Test + public void testUserLoginWithDefaultConfig() { + /* + * Test most basic user login, using the default admin-cli as a client + */ + KcAdmExec exe = KcAdmExec.execute("config credentials --server " + serverUrl + " --realm master --user admin --password admin"); + + assertExitCodeAndStreamSizes(exe, 0, 0, 1); + Assert.assertEquals("stderr first line", "Logging into " + serverUrl + " as user admin of realm master", exe.stderrLines().get(0)); + } + + @Test + public void testUserLoginWithDefaultConfigInteractive() throws IOException { + /* + * Test user login with interaction - provide user password after prompted for it + */ + + if (!runIntermittentlyFailingTests()) { + System.out.println("TEST SKIPPED - This test currently suffers from intermittent failures. Use -Dtest.intermittent=true to run it."); + return; + } + + KcAdmExec exe = KcAdmExec.newBuilder() + .argsLine("config credentials --server " + serverUrl + " --realm master --user admin") + .executeAsync(); + + exe.waitForStdout("Enter password: "); + exe.sendToStdin("admin" + EOL); + exe.waitCompletion(); + + assertExitCodeAndStreamSizes(exe, 0, 1, 1); + Assert.assertEquals("stderr first line", "Logging into " + serverUrl + " as user admin of realm master", exe.stderrLines().get(0)); + + + /* + * Run the test one more time with stdin redirect + */ + File tmpFile = new File(KcAdmExec.WORK_DIR + "/" + UUID.randomUUID().toString() + ".tmp"); + try { + FileOutputStream tmpos = new FileOutputStream(tmpFile); + tmpos.write("admin".getBytes()); + tmpos.write(EOL.getBytes()); + tmpos.close(); + + exe = KcAdmExec.execute("config credentials --server " + serverUrl + " --realm master --user admin < '" + tmpFile.getName() + "'"); + + assertExitCodeAndStreamSizes(exe, 0, 1, 1); + Assert.assertTrue("Enter password prompt", exe.stdoutLines().get(0).startsWith("Enter password: ")); + Assert.assertEquals("stderr first line", "Logging into " + serverUrl + " as user admin of realm master", exe.stderrLines().get(0)); + + } finally { + tmpFile.delete(); + } + } + + @Test + public void testClientLoginWithDefaultConfigInteractive() throws IOException { + /* + * Test client login with interaction - login using service account, and provide a client secret after prompted for it + */ + + if (!runIntermittentlyFailingTests()) { + System.out.println("TEST SKIPPED - This test currently suffers from intermittent failures. Use -Dtest.intermittent=true to run it."); + return; + } + + // use -Dtest.intermittent=true to run this test + KcAdmExec exe = KcAdmExec.newBuilder() + .argsLine("config credentials --server " + serverUrl + " --realm test --client admin-cli-secret") + .executeAsync(); + + exe.waitForStdout("Enter client secret: "); + exe.sendToStdin("password" + EOL); + exe.waitCompletion(); + + assertExitCodeAndStreamSizes(exe, 0, 1, 1); + Assert.assertEquals("stderr first line", "Logging into " + serverUrl + " as service-account-admin-cli-secret of realm test", exe.stderrLines().get(0)); + + /* + * Run the test one more time with stdin redirect + */ + File tmpFile = new File(KcAdmExec.WORK_DIR + "/" + UUID.randomUUID().toString() + ".tmp"); + try { + FileOutputStream tmpos = new FileOutputStream(tmpFile); + tmpos.write("password".getBytes()); + tmpos.write(EOL.getBytes()); + tmpos.close(); + + exe = KcAdmExec.newBuilder() + .argsLine("config credentials --server " + serverUrl + " --realm test --client admin-cli-secret < '" + tmpFile.getName() + "'") + .execute(); + + assertExitCodeAndStreamSizes(exe, 0, 1, 1); + Assert.assertTrue("Enter client secret prompt", exe.stdoutLines().get(0).startsWith("Enter client secret: ")); + Assert.assertEquals("stderr first line", "Logging into " + serverUrl + " as service-account-admin-cli-secret of realm test", exe.stderrLines().get(0)); + } finally { + tmpFile.delete(); + } + } + + @Test + public void testUserLoginWithCustomConfig() { + /* + * Test user login using a custom config file + */ + FileConfigHandler handler = initCustomConfigFile(); + + File configFile = new File(handler.getConfigFile()); + try { + KcAdmExec exe = KcAdmExec.execute("config credentials --server " + serverUrl + " --realm master" + + " --user admin --password admin --config '" + configFile.getName() + "'"); + + assertExitCodeAndStreamSizes(exe, 0, 0, 1); + Assert.assertEquals("stderr first line", "Logging into " + serverUrl + " as user admin of realm master", exe.stderrLines().get(0)); + + // make sure the config file exists, and has the right content + ConfigData config = handler.loadConfig(); + Assert.assertEquals("serverUrl", serverUrl, config.getServerUrl()); + Assert.assertEquals("realm", "master", config.getRealm()); + RealmConfigData realmcfg = config.sessionRealmConfigData(); + Assert.assertNotNull("realm config data no null", realmcfg); + Assert.assertEquals("realm cfg serverUrl", serverUrl, realmcfg.serverUrl()); + Assert.assertEquals("realm cfg realm", "master", realmcfg.realm()); + Assert.assertEquals("client id", "admin-cli", realmcfg.getClientId()); + Assert.assertNotNull("token not null", realmcfg.getToken()); + Assert.assertNotNull("refresh token not null", realmcfg.getRefreshToken()); + Assert.assertNotNull("token expires not null", realmcfg.getExpiresAt()); + Assert.assertNotNull("token expires in future", realmcfg.getExpiresAt() > System.currentTimeMillis()); + Assert.assertNotNull("refresh token expires not null", realmcfg.getRefreshExpiresAt()); + Assert.assertNotNull("refresh token expires in future", realmcfg.getRefreshExpiresAt() > System.currentTimeMillis()); + + } finally { + configFile.delete(); + } + } + + @Test + public void testCustomConfigLoginCreateDelete() throws IOException { + /* + * Test user login, create, delete session using a custom config file + */ + + // prepare for loading a config file + FileConfigHandler handler = initCustomConfigFile(); + + try (TempFileResource configFile = new TempFileResource(handler.getConfigFile())) { + + KcAdmExec exe = KcAdmExec.execute("config credentials --server " + serverUrl + + " --realm master --user admin --password admin --config '" + configFile.getName() + "'"); + + assertExitCodeAndStreamSizes(exe, 0, 0, 1); + + // remember the state of config file + ConfigData config1 = handler.loadConfig(); + + + + + exe = KcAdmExec.execute("create --config '" + configFile.getName() + "' clients -s clientId=test-client -o"); + + assertExitCodeAndStdErrSize(exe, 0, 0); + + // check changes to config file + ConfigData config2 = handler.loadConfig(); + assertFieldsEqualWithExclusions(config1, config2); + + + ClientRepresentation client = JsonSerialization.readValue(exe.stdout(), ClientRepresentation.class); + Assert.assertEquals("clientId", "test-client", client.getClientId()); + + + + exe = KcAdmExec.execute("delete clients/" + client.getId() + " --config '" + configFile.getName() + "'"); + + assertExitCodeAndStreamSizes(exe, 0, 0, 0); + + // check changes to config file + ConfigData config3 = handler.loadConfig(); + assertFieldsEqualWithExclusions(config2, config3); + } + } + + @Test + public void testCRUDWithOnTheFlyUserAuth() throws IOException { + /* + * Test create, get, update, and delete using on-the-fly authentication - without using any config file. + * Login is performed by each operation again, and again using username, and password. + */ + testCRUDWithOnTheFlyAuth(serverUrl, "--user user1 --password userpass", "", + "Logging into " + serverUrl + " as user user1 of realm test"); + } + + @Test + public void testCRUDWithOnTheFlyUserAuthWithClientSecret() throws IOException { + /* + * Test create, get, update, and delete using on-the-fly authentication - without using any config file. + * Login is performed by each operation again, and again using username, password, and client secret. + */ + // try client without direct grants enabled + KcAdmExec exe = KcAdmExec.execute("get clients --no-config --server " + serverUrl + " --realm test" + + " --user user1 --password userpass --client admin-cli-secret --secret password"); + + assertExitCodeAndStreamSizes(exe, 1, 0, 2); + Assert.assertEquals("login message", "Logging into " + serverUrl + " as user user1 of realm test", exe.stderrLines().get(0)); + Assert.assertEquals("error message", "Client not allowed for direct access grants [invalid_grant]", exe.stderrLines().get(1)); + + + // try wrong user password + exe = KcAdmExec.execute("get clients --no-config --server " + serverUrl + " --realm test" + + " --user user1 --password wrong --client admin-cli-secret-direct --secret password"); + + assertExitCodeAndStreamSizes(exe, 1, 0, 2); + Assert.assertEquals("login message", "Logging into " + serverUrl + " as user user1 of realm test", exe.stderrLines().get(0)); + Assert.assertEquals("error message", "Invalid user credentials [invalid_grant]", exe.stderrLines().get(1)); + + + // try wrong client secret + exe = KcAdmExec.execute("get clients --no-config --server " + serverUrl + " --realm test" + + " --user user1 --password userpass --client admin-cli-secret-direct --secret wrong"); + + assertExitCodeAndStreamSizes(exe, 1, 0, 2); + Assert.assertEquals("login message", "Logging into " + serverUrl + " as user user1 of realm test", exe.stderrLines().get(0)); + Assert.assertEquals("error message", "Invalid client secret [unauthorized_client]", exe.stderrLines().get(1)); + + + // try whole CRUD + testCRUDWithOnTheFlyAuth(serverUrl, "--user user1 --password userpass --client admin-cli-secret-direct --secret password", "", + "Logging into " + serverUrl + " as user user1 of realm test"); + } + + @Test + public void testCRUDWithOnTheFlyUserAuthWithSignedJwtClient() throws IOException { + /* + * Test create, get, update, and delete using on-the-fly authentication - without using any config file. + * Login is performed by each operation again, and again using username, password, and client JWT signature. + */ + File keystore = new File(System.getProperty("user.dir") + "/src/test/resources/cli/kcadm/admin-cli-keystore.jks"); + Assert.assertTrue("admin-cli-keystore.jks exists", keystore.isFile()); + + // try client without direct grants enabled + KcAdmExec exe = KcAdmExec.execute("get clients --no-config --server " + serverUrl + " --realm test" + + " --user user1 --password userpass --client admin-cli-jwt --keystore '" + keystore.getAbsolutePath() + "'" + + " --storepass storepass --keypass keypass --alias admin-cli"); + + assertExitCodeAndStreamSizes(exe, 1, 0, 2); + Assert.assertEquals("login message", "Logging into " + serverUrl + " as user user1 of realm test", exe.stderrLines().get(0)); + Assert.assertEquals("error message", "Client not allowed for direct access grants [invalid_grant]", exe.stderrLines().get(1)); + + + // try wrong user password + exe = KcAdmExec.execute("get clients --no-config --server " + serverUrl + " --realm test" + + " --user user1 --password wrong --client admin-cli-jwt-direct --keystore '" + keystore.getAbsolutePath() + "'" + + " --storepass storepass --keypass keypass --alias admin-cli"); + + assertExitCodeAndStreamSizes(exe, 1, 0, 2); + Assert.assertEquals("login message", "Logging into " + serverUrl + " as user user1 of realm test", exe.stderrLines().get(0)); + Assert.assertEquals("error message", "Invalid user credentials [invalid_grant]", exe.stderrLines().get(1)); + + + // try wrong storepass + exe = KcAdmExec.execute("get clients --no-config --server " + serverUrl + " --realm test" + + " --user user1 --password userpass --client admin-cli-jwt-direct --keystore '" + keystore.getAbsolutePath() + "'" + + " --storepass wrong --keypass keypass --alias admin-cli"); + + assertExitCodeAndStreamSizes(exe, 1, 0, 2); + Assert.assertEquals("login message", "Logging into " + serverUrl + " as user user1 of realm test", exe.stderrLines().get(0)); + Assert.assertEquals("error message", "Failed to load private key: Keystore was tampered with, or password was incorrect", exe.stderrLines().get(1)); + + + // try whole CRUD + testCRUDWithOnTheFlyAuth(serverUrl, + "--user user1 --password userpass --client admin-cli-jwt-direct --keystore '" + keystore.getAbsolutePath() + "'" + + " --storepass storepass --keypass keypass --alias admin-cli", "", + "Logging into " + serverUrl + " as user user1 of realm test"); + + } + + @Test + public void testCRUDWithOnTheFlyServiceAccountWithClientSecret() throws IOException { + /* + * Test create, get, update, and delete using on-the-fly authentication - without using any config file. + * Login is performed by each operation again, and again using only client secret - service account is used. + */ + testCRUDWithOnTheFlyAuth(serverUrl, "--client admin-cli-secret --secret password", "", + "Logging into " + serverUrl + " as service-account-admin-cli-secret of realm test"); + } + + @Test + public void testCRUDWithOnTheFlyServiceAccountWithSignedJwtClient() throws IOException { + /* + * Test create, get, update, and delete using on-the-fly authentication - without using any config file. + * Login is performed by each operation again, and again using only client JWT signature - service account is used. + */ + File keystore = new File(System.getProperty("user.dir") + "/src/test/resources/cli/kcadm/admin-cli-keystore.jks"); + Assert.assertTrue("admin-cli-keystore.jks exists", keystore.isFile()); + + testCRUDWithOnTheFlyAuth(serverUrl, + "--client admin-cli-jwt --keystore '" + keystore.getAbsolutePath() + "' --storepass storepass --keypass keypass --alias admin-cli", "", + "Logging into " + serverUrl + " as service-account-admin-cli-jwt of realm test"); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmTruststoreTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmTruststoreTest.java new file mode 100644 index 0000000000..1346442b79 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmTruststoreTest.java @@ -0,0 +1,115 @@ +package org.keycloak.testsuite.cli.admin; + +import org.junit.Assert; +import org.junit.Test; +import org.keycloak.client.admin.cli.config.ConfigData; +import org.keycloak.client.admin.cli.config.FileConfigHandler; +import org.keycloak.testsuite.cli.KcAdmExec; +import org.keycloak.testsuite.util.TempFileResource; + +import java.io.File; +import java.io.IOException; + +import static org.keycloak.client.admin.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_PATH; +import static org.keycloak.client.admin.cli.util.OsUtil.EOL; +import static org.keycloak.testsuite.cli.KcAdmExec.CMD; +import static org.keycloak.testsuite.cli.KcAdmExec.execute; + +/** + * @author Marko Strukelj + */ +public class KcAdmTruststoreTest extends AbstractAdmCliTest { + + @Test + public void testTruststore() throws IOException { + + // only run this test if ssl protected keycloak server is available + if (!isAuthServerSSL()) { + System.out.println("TEST SKIPPED - This test requires HTTPS. Run with '-Pauth-server-wildfly -Dauth.server.ssl.required=true'"); + return; + } + + File truststore = new File("src/test/resources/keystore/keycloak.truststore"); + + FileConfigHandler handler = initCustomConfigFile(); + + try (TempFileResource configFile = new TempFileResource(handler.getConfigFile())) { + + if (runIntermittentlyFailingTests()) { + // configure truststore + KcAdmExec exe = execute("config truststore --config '" + configFile.getName() + "' '" + truststore.getAbsolutePath() + "'"); + + assertExitCodeAndStreamSizes(exe, 0, 0, 0); + + + // perform authentication against server - asks for password, then for truststore password + exe = KcAdmExec.newBuilder() + .argsLine("config credentials --server " + serverUrl + " --realm test --user user1" + + " --config '" + configFile.getName() + "'") + .executeAsync(); + + exe.waitForStdout("Enter password: "); + exe.sendToStdin("userpass" + EOL); + exe.waitForStdout("Enter truststore password: "); + exe.sendToStdin("secret" + EOL); + exe.waitCompletion(); + + assertExitCodeAndStreamSizes(exe, 0, 2, 1); + + + // configure truststore with password + exe = execute("config truststore --config '" + configFile.getName() + "' --trustpass secret '" + truststore.getAbsolutePath() + "'"); + + assertExitCodeAndStreamSizes(exe, 0, 0, 0); + + // perform authentication against server - asks for password, then for truststore password + exe = KcAdmExec.newBuilder() + .argsLine("config credentials --server " + serverUrl + " --realm test --user user1" + + " --config '" + configFile.getName() + "'") + .executeAsync(); + + exe.waitForStdout("Enter password: "); + exe.sendToStdin("userpass" + EOL); + exe.waitCompletion(); + + assertExitCodeAndStreamSizes(exe, 0, 1, 1); + + } else { + System.out.println("TEST SKIPPED PARTIALLY - This test currently suffers from intermittent failures. Use -Dtest.intermittent=true to run it in full."); + } + } + + // configure truststore with password + KcAdmExec exe = execute("config truststore --trustpass secret '" + truststore.getAbsolutePath() + "'"); + assertExitCodeAndStreamSizes(exe, 0, 0, 0); + + // perform authentication against server - asks for password, then for truststore password + exe = execute("config credentials --server " + serverUrl + " --realm test --user user1 --password userpass"); + assertExitCodeAndStreamSizes(exe, 0, 0, 1); + + exe = execute("config truststore --delete"); + assertExitCodeAndStreamSizes(exe, 0, 0, 0); + + exe = execute("config truststore --delete '" + truststore.getAbsolutePath() + "'"); + assertExitCodeAndStreamSizes(exe, 1, 0, 2); + Assert.assertEquals("incompatible", "Option --delete is mutually exclusive with specifying a TRUSTSTORE", exe.stderrLines().get(0)); + Assert.assertEquals("try help", "Try '" + CMD + " help config truststore' for more information", exe.stderrLines().get(1)); + + exe = execute("config truststore --delete --trustpass secret"); + assertExitCodeAndStreamSizes(exe, 1, 0, 2); + Assert.assertEquals("no truststore error", "Options --trustpass and --delete are mutually exclusive", exe.stderrLines().get(0)); + Assert.assertEquals("try help", "Try '" + CMD + " help config truststore' for more information", exe.stderrLines().get(1)); + + FileConfigHandler cfghandler = new FileConfigHandler(); + cfghandler.setConfigFile(DEFAULT_CONFIG_FILE_PATH); + ConfigData config = cfghandler.loadConfig(); + Assert.assertNull("truststore null", config.getTruststore()); + Assert.assertNull("trustpass null", config.getTrustpass()); + + + // perform no-config CRUD test against ssl protected endpoint + testCRUDWithOnTheFlyAuth(serverUrl, + "--user user1 --password userpass", " --truststore '" + truststore.getAbsolutePath() + "' --trustpass secret", + "Logging into " + serverUrl + " as user user1 of realm test"); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmUpdateTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmUpdateTest.java new file mode 100644 index 0000000000..6e75f599ee --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmUpdateTest.java @@ -0,0 +1,130 @@ +package org.keycloak.testsuite.cli.admin; + +import org.junit.Assert; +import org.junit.Test; +import org.keycloak.client.admin.cli.config.FileConfigHandler; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.testsuite.cli.KcAdmExec; +import org.keycloak.testsuite.util.TempFileResource; +import org.keycloak.util.JsonSerialization; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.Arrays; + +import static org.keycloak.testsuite.cli.KcAdmExec.CMD; +import static org.keycloak.testsuite.cli.KcAdmExec.execute; + +/** + * @author Marko Strukelj + */ +public class KcAdmUpdateTest extends AbstractAdmCliTest { + + @Test + public void testUpdateThoroughly() throws IOException { + + FileConfigHandler handler = initCustomConfigFile(); + + try (TempFileResource configFile = new TempFileResource(handler.getConfigFile())) { + + final String realm = "test"; + + loginAsUser(configFile.getFile(), serverUrl, realm, "user1", "userpass"); + + + // create an object so we can update it + KcAdmExec exe = execute("create clients --config '" + configFile.getName() + "' -o -s clientId=my_client"); + + assertExitCodeAndStdErrSize(exe, 0, 0); + + ClientRepresentation client = JsonSerialization.readValue(exe.stdout(), ClientRepresentation.class); + + Assert.assertEquals("enabled", true, client.isEnabled()); + Assert.assertEquals("publicClient", false, client.isPublicClient()); + Assert.assertEquals("bearerOnly", false, client.isBearerOnly()); + Assert.assertTrue("redirectUris is empty", client.getRedirectUris().isEmpty()); + + + // Merge update + exe = execute("update clients/" + client.getId() + " --config '" + configFile.getName() + "' -o " + + " -s enabled=false -s 'redirectUris=[\"http://localhost:8980/myapp/*\"]'"); + + assertExitCodeAndStdErrSize(exe, 0, 0); + + client = JsonSerialization.readValue(exe.stdout(), ClientRepresentation.class); + Assert.assertEquals("enabled", false, client.isEnabled()); + Assert.assertEquals("redirectUris", Arrays.asList("http://localhost:8980/myapp/*"), client.getRedirectUris()); + + + + // Another merge update - test deleting an attribute, deleting a list item and adding a list item + exe = execute("update clients/" + client.getId() + " --config '" + configFile.getName() + "' -o -d redirectUris[0] -s webOrigins+=http://localhost:8980/myapp -s webOrigins+=http://localhost:8981/myapp -d webOrigins[0]"); + + assertExitCodeAndStdErrSize(exe, 0, 0); + + client = JsonSerialization.readValue(exe.stdout(), ClientRepresentation.class); + + Assert.assertTrue("redirectUris is empty", client.getRedirectUris().isEmpty()); + Assert.assertEquals("webOrigins", Arrays.asList("http://localhost:8981/myapp"), client.getWebOrigins()); + + + + // Another merge update - test nested attributes and setting an attribute using json format + // TODO KEYCLOAK-3705 Updating protocolMapper config via client registration endpoint has no effect + /* + exe = execute("update my_client --config '" + configFile.getName() + "' -o -s 'protocolMappers[0].config.\"id.token.claim\"=false' " + + "-s 'protocolMappers[4].config={\"single\": \"true\", \"attribute.nameformat\": \"Basic\", \"attribute.name\": \"Role\"}'"); + + assertExitCodeAndStdErrSize(exe, 0, 0); + + client = JsonSerialization.readValue(exe.stdout(), ClientRepresentation.class); + Assert.assertEquals("protocolMapper[0].config.\"id.token.claim\"", "false", client.getProtocolMappers().get(0).getConfig().get("id.token.claim")); + Assert.assertEquals("protocolMappers[4].config.single", "true", client.getProtocolMappers().get(4).getConfig().get("single")); + Assert.assertEquals("protocolMappers[4].config.\"attribute.nameformat\"", "Basic", client.getProtocolMappers().get(4).getConfig().get("attribute.nameformat")); + Assert.assertEquals("protocolMappers[4].config.\"attribute.name\"", "Role", client.getProtocolMappers().get(4).getConfig().get("attribute.name")); + */ + + // update using oidc format + + + // check that using an invalid attribute key is not ignored + exe = execute("update clients/" + client.getId() + " --nonexisting --config '" + configFile.getName() + "'"); + + assertExitCodeAndStreamSizes(exe, 1, 0, 2); + Assert.assertEquals("error message", "Invalid option: --nonexisting", exe.stderrLines().get(0)); + Assert.assertEquals("try help", "Try '" + CMD + " help update' for more information", exe.stderrLines().get(1)); + + + // test overwrite from file + exe = KcAdmExec.newBuilder() + .argsLine("update clients/" + client.getId() + " --config '" + configFile.getName() + + "' -o -s clientId=my_client -s 'redirectUris=[\"http://localhost:8980/myapp/*\"]' -f -") + .stdin(new ByteArrayInputStream("{ \"enabled\": false }".getBytes())) + .execute(); + + assertExitCodeAndStdErrSize(exe, 0, 0); + + client = JsonSerialization.readValue(exe.stdout(), ClientRepresentation.class); + // web origin is not sent to the server, thus it retains the current value + Assert.assertEquals("webOrigins", Arrays.asList("http://localhost:8981/myapp"), client.getWebOrigins()); + Assert.assertFalse("enabled is false", client.isEnabled()); + Assert.assertEquals("redirectUris", Arrays.asList("http://localhost:8980/myapp/*"), client.getRedirectUris()); + + + // test using merge with file + exe = KcAdmExec.newBuilder() + .argsLine("update clients/" + client.getId() + " --config '" + configFile.getName() + + "' -o -s enabled=true -m -f -") + .stdin(new ByteArrayInputStream("{ \"webOrigins\": [\"http://localhost:8980/myapp\"] }".getBytes())) + .execute(); + + assertExitCodeAndStdErrSize(exe, 0, 0); + + + client = JsonSerialization.readValue(exe.stdout(), ClientRepresentation.class); + Assert.assertEquals("webOrigins", Arrays.asList("http://localhost:8980/myapp"), client.getWebOrigins()); + Assert.assertTrue("enabled is true", client.isEnabled()); + Assert.assertEquals("redirectUris", Arrays.asList("http://localhost:8980/myapp/*"), client.getRedirectUris()); + } + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/AbstractCliTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/AbstractRegCliTest.java similarity index 93% rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/AbstractCliTest.java rename to testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/AbstractRegCliTest.java index 1098053657..40d755a090 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/AbstractCliTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/AbstractRegCliTest.java @@ -19,7 +19,7 @@ import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy; import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyManager; import org.keycloak.services.clientregistration.policy.RegistrationAuth; import org.keycloak.services.clientregistration.policy.impl.TrustedHostClientRegistrationPolicyFactory; -import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.cli.AbstractCliTest; import org.keycloak.testsuite.cli.KcRegExec; import org.keycloak.testsuite.util.ClientBuilder; import org.keycloak.testsuite.util.UserBuilder; @@ -45,7 +45,7 @@ import static org.keycloak.testsuite.cli.KcRegExec.execute; /** * @author Marko Strukelj */ -public abstract class AbstractCliTest extends AbstractKeycloakTest { +public abstract class AbstractRegCliTest extends AbstractCliTest { protected String serverUrl = isAuthServerSSL() ? "https://localhost:" + getAuthServerHttpsPort() + "/auth" : @@ -527,43 +527,4 @@ public abstract class AbstractCliTest extends AbstractKeycloakTest { lastModified2 = configFile.exists() ? configFile.lastModified() : 0; Assert.assertEquals("config file not modified", lastModified, lastModified2); } - - void assertExitCodeAndStdOutSize(KcRegExec exe, int exitCode, int stdOutLineCount) { - assertExitCodeAndStreamSizes(exe, exitCode, stdOutLineCount, -1); - } - - void assertExitCodeAndStdErrSize(KcRegExec exe, int exitCode, int stdErrLineCount) { - assertExitCodeAndStreamSizes(exe, exitCode, -1, stdErrLineCount); - } - - void assertExitCodeAndStreamSizes(KcRegExec exe, int exitCode, int stdOutLineCount, int stdErrLineCount) { - Assert.assertEquals("exitCode == " + exitCode, exitCode, exe.exitCode()); - if (stdOutLineCount != -1) { - try { - assertLineCount("stdout output", exe.stdoutLines(), stdOutLineCount); - } catch (Throwable e) { - throw new AssertionError("STDOUT: " + exe.stdoutString(), e); - } - } - if (stdErrLineCount != -1) { - try { - assertLineCount("stderr output", exe.stderrLines(), stdErrLineCount); - } catch (Throwable e) { - throw new AssertionError("STDERR: " + exe.stderrString(), e); - } - } - } - - void assertLineCount(String label, List lines, int count) { - if (lines.size() == count) { - return; - } - // there is some kind of race condition in 'kcreg' that results in intermittent extra empty line - if (lines.size() == count + 1) { - if ("".equals(lines.get(lines.size()-1))) { - return; - } - } - Assert.assertTrue(label + " has " + lines.size() + " lines (expected: " + count + ")", lines.size() == count); - } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegConfigTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegConfigTest.java index b91296cead..1bf7b3896e 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegConfigTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegConfigTest.java @@ -15,7 +15,7 @@ import static org.keycloak.testsuite.cli.KcRegExec.execute; /** * @author Marko Strukelj */ -public class KcRegConfigTest extends AbstractCliTest { +public class KcRegConfigTest extends AbstractRegCliTest { @Test public void testRegistrationToken() throws IOException { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegCreateTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegCreateTest.java index 5db5d86cd0..d916a9b149 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegCreateTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegCreateTest.java @@ -19,7 +19,7 @@ import static org.keycloak.testsuite.cli.KcRegExec.execute; /** * @author Marko Strukelj */ -public class KcRegCreateTest extends AbstractCliTest { +public class KcRegCreateTest extends AbstractRegCliTest { @Test public void testCreateWithRealmOverride() throws IOException { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegTest.java index 5647ec881f..872ed320d0 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegTest.java @@ -23,7 +23,7 @@ import static org.keycloak.testsuite.cli.KcRegExec.execute; /** * @author Marko Strukelj */ -public class KcRegTest extends AbstractCliTest { +public class KcRegTest extends AbstractRegCliTest { @Test public void testNoArgs() { @@ -68,7 +68,7 @@ public class KcRegTest extends AbstractCliTest { exe = execute("config truststore"); assertExitCodeAndStdErrSize(exe, 1, 0); Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10); - Assert.assertEquals("help message", "Usage: " + CMD + " config truststore [TRUSTSTORE | --delete] [--trustpass PASSWOD] [ARGUMENTS]", exe.stdoutLines().get(0)); + Assert.assertEquals("help message", "Usage: " + CMD + " config truststore [TRUSTSTORE | --delete] [--trustpass PASSWORD] [ARGUMENTS]", exe.stdoutLines().get(0)); exe = execute("create"); assertExitCodeAndStdErrSize(exe, 1, 0); @@ -172,7 +172,7 @@ public class KcRegTest extends AbstractCliTest { exe = execute("config truststore --help"); assertExitCodeAndStdErrSize(exe, 0, 0); Assert.assertEquals("stdout first line", - "Usage: " + CMD + " config truststore [TRUSTSTORE | --delete] [--trustpass PASSWOD] [ARGUMENTS]", + "Usage: " + CMD + " config truststore [TRUSTSTORE | --delete] [--trustpass PASSWORD] [ARGUMENTS]", exe.stdoutLines().get(0)); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegTruststoreTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegTruststoreTest.java index e3f7729264..34c9584fa2 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegTruststoreTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegTruststoreTest.java @@ -18,7 +18,7 @@ import static org.keycloak.testsuite.cli.KcRegExec.execute; /** * @author Marko Strukelj */ -public class KcRegTruststoreTest extends AbstractCliTest { +public class KcRegTruststoreTest extends AbstractRegCliTest { @Test public void testTruststore() throws IOException { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegUpdateTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegUpdateTest.java index 79e670793e..419d75469a 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegUpdateTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegUpdateTest.java @@ -18,7 +18,7 @@ import static org.keycloak.testsuite.cli.KcRegExec.execute; /** * @author Marko Strukelj */ -public class KcRegUpdateTest extends AbstractCliTest { +public class KcRegUpdateTest extends AbstractRegCliTest { @Test diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegUpdateTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegUpdateTokenTest.java index b2aebf77c4..a4b6c2cbd4 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegUpdateTokenTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/registration/KcRegUpdateTokenTest.java @@ -18,7 +18,7 @@ import static org.keycloak.testsuite.cli.KcRegExec.execute; /** * @author Marko Strukelj */ -public class KcRegUpdateTokenTest extends AbstractCliTest { +public class KcRegUpdateTokenTest extends AbstractRegCliTest { @Test public void testUpdateToken() throws IOException { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java index 2abf0447d8..5d5b3438df 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java @@ -99,9 +99,9 @@ public class ExportImportTest extends AbstractExportImportTest { testRealmExportImport(); - // There should be 3 files in target directory (1 realm, 3 user) + // There should be 3 files in target directory (1 realm, 4 user) File[] files = new File(targetDirPath).listFiles(); - assertEquals(4, files.length); + assertEquals(5, files.length); } @Test diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java index 3e16c4b0d2..3c296a12cf 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java @@ -379,7 +379,7 @@ public class ExportImportUtil { Assert.assertNotNull(linked); Assert.assertEquals("my-service-user", linked.getUsername()); - if (Profile.isPreviewEnabled()) { + if (Profile.isFeatureEnabled(Profile.Feature.AUTHORIZATION)) { assertAuthorizationSettings(realmRsc); } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java index 707b765290..1a2149d775 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java @@ -62,12 +62,12 @@ public class RegisterTest extends TestRealmKeycloakTest { } @Test - public void registerExistingUser() { + public void registerExistingUsernameForbidden() { loginPage.open(); loginPage.clickRegister(); registerPage.assertCurrent(); - registerPage.register("firstName", "lastName", "registerExistingUser@email", "test-user@localhost", "password", "password"); + registerPage.register("firstName", "lastName", "registerExistingUser@email", "roleRichUser", "password", "password"); registerPage.assertCurrent(); assertEquals("Username already exists.", registerPage.getError()); @@ -80,10 +80,57 @@ public class RegisterTest extends TestRealmKeycloakTest { assertEquals("", registerPage.getPassword()); assertEquals("", registerPage.getPasswordConfirm()); - events.expectRegister("test-user@localhost", "registerExistingUser@email") + events.expectRegister("roleRichUser", "registerExistingUser@email") .removeDetail(Details.EMAIL) .user((String) null).error("username_in_use").assertEvent(); } + + @Test + public void registerExistingEmailForbidden() { + loginPage.open(); + loginPage.clickRegister(); + registerPage.assertCurrent(); + + registerPage.register("firstName", "lastName", "test-user@localhost", "registerExistingUser", "password", "password"); + + registerPage.assertCurrent(); + assertEquals("Email already exists.", registerPage.getError()); + + // assert form keeps form fields on error + assertEquals("firstName", registerPage.getFirstName()); + assertEquals("lastName", registerPage.getLastName()); + assertEquals("", registerPage.getEmail()); + assertEquals("registerExistingUser", registerPage.getUsername()); + assertEquals("", registerPage.getPassword()); + assertEquals("", registerPage.getPasswordConfirm()); + + events.expectRegister("registerExistingUser", "registerExistingUser@email") + .removeDetail(Details.EMAIL) + .user((String) null).error("email_in_use").assertEvent(); + } + + @Test + public void registerExistingEmailAllowed() { + setDuplicateEmailsAllowed(true); + + loginPage.open(); + loginPage.clickRegister(); + registerPage.assertCurrent(); + + registerPage.register("firstName", "lastName", "test-user@localhost", "registerExistingEmailUser", "password", "password"); + + assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); + + String userId = events.expectRegister("registerExistingEmailUser", "test-user@localhost").assertEvent().getUserId(); + events.expectLogin().detail("username", "registerexistingemailuser").user(userId).assertEvent(); + + UserRepresentation user = getUser(userId); + Assert.assertNotNull(user); + assertEquals("registerexistingemailuser", user.getUsername()); + assertEquals("test-user@localhost", user.getEmail()); + assertEquals("firstName", user.getFirstName()); + assertEquals("lastName", user.getLastName()); + } @Test public void registerUserInvalidPasswordConfirm() { @@ -397,5 +444,11 @@ public class RegisterTest extends TestRealmKeycloakTest { realm.setRegistrationEmailAsUsername(value); testRealm().update(realm); } + + private void setDuplicateEmailsAllowed(boolean allowed) { + RealmRepresentation testRealm = testRealm().toRepresentation(); + testRealm.setDuplicateEmailsAllowed(allowed); + testRealm().update(testRealm); + } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/GeneratedHmacKeyProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/GeneratedHmacKeyProviderTest.java new file mode 100644 index 0000000000..fb3a4f74ca --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/GeneratedHmacKeyProviderTest.java @@ -0,0 +1,182 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.testsuite.keys; + +import org.jboss.arquillian.graphene.page.Page; +import org.junit.Rule; +import org.junit.Test; +import org.keycloak.common.util.Base64Url; +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.common.util.PemUtils; +import org.keycloak.jose.jws.AlgorithmType; +import org.keycloak.jose.jws.crypto.HMACProvider; +import org.keycloak.keys.GeneratedHmacKeyProviderFactory; +import org.keycloak.keys.GeneratedRsaKeyProviderFactory; +import org.keycloak.keys.KeyProvider; +import org.keycloak.representations.idm.ComponentRepresentation; +import org.keycloak.representations.idm.ErrorRepresentation; +import org.keycloak.representations.idm.KeysMetadataRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.AssertEvents; +import org.keycloak.testsuite.admin.ApiUtil; +import org.keycloak.testsuite.pages.AppPage; +import org.keycloak.testsuite.pages.LoginPage; + +import javax.ws.rs.core.Response; +import java.security.interfaces.RSAPublicKey; +import java.util.List; + +import static org.junit.Assert.*; +import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson; + +/** + * @author Stian Thorgersen + */ +public class GeneratedHmacKeyProviderTest extends AbstractKeycloakTest { + + @Rule + public AssertEvents events = new AssertEvents(this); + + @Page + protected AppPage appPage; + + @Page + protected LoginPage loginPage; + + @Override + public void addTestRealms(List testRealms) { + RealmRepresentation realm = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class); + testRealms.add(realm); + } + + @Test + public void defaultKeysize() throws Exception { + long priority = System.currentTimeMillis(); + + ComponentRepresentation rep = createRep("valid", GeneratedHmacKeyProviderFactory.ID); + rep.setConfig(new MultivaluedHashMap<>()); + rep.getConfig().putSingle("priority", Long.toString(priority)); + + Response response = adminClient.realm("test").components().add(rep); + String id = ApiUtil.getCreatedId(response); + + ComponentRepresentation createdRep = adminClient.realm("test").components().component(id).toRepresentation(); + assertEquals(1, createdRep.getConfig().size()); + assertEquals(Long.toString(priority), createdRep.getConfig().getFirst("priority")); + + KeysMetadataRepresentation keys = adminClient.realm("test").keys().getKeyMetadata(); + + KeysMetadataRepresentation.KeyMetadataRepresentation key = null; + for (KeysMetadataRepresentation.KeyMetadataRepresentation k : keys.getKeys()) { + if (k.getType().equals(AlgorithmType.HMAC.name())) { + key = k; + break; + } + } + + assertEquals(id, key.getProviderId()); + assertEquals(AlgorithmType.HMAC.name(), key.getType()); + assertEquals(priority, key.getProviderPriority()); + + assertEquals(32, Base64Url.decode(testingClient.testing("test").getComponentConfig(id).getFirst("secret")).length); + } + + @Test + public void largeKeysize() throws Exception { + long priority = System.currentTimeMillis(); + + ComponentRepresentation rep = createRep("valid", GeneratedHmacKeyProviderFactory.ID); + rep.setConfig(new MultivaluedHashMap<>()); + rep.getConfig().putSingle("priority", Long.toString(priority)); + rep.getConfig().putSingle("secretSize", "512"); + + Response response = adminClient.realm("test").components().add(rep); + String id = ApiUtil.getCreatedId(response); + + ComponentRepresentation createdRep = adminClient.realm("test").components().component(id).toRepresentation(); + assertEquals(2, createdRep.getConfig().size()); + assertEquals("512", createdRep.getConfig().getFirst("secretSize")); + + KeysMetadataRepresentation keys = adminClient.realm("test").keys().getKeyMetadata(); + + KeysMetadataRepresentation.KeyMetadataRepresentation key = null; + for (KeysMetadataRepresentation.KeyMetadataRepresentation k : keys.getKeys()) { + if (k.getType().equals(AlgorithmType.HMAC.name())) { + key = k; + break; + } + } + + assertEquals(id, key.getProviderId()); + assertEquals(AlgorithmType.HMAC.name(), key.getType()); + assertEquals(priority, key.getProviderPriority()); + + assertEquals(512, Base64Url.decode(testingClient.testing("test").getComponentConfig(id).getFirst("secret")).length); + } + + @Test + public void updateKeysize() throws Exception { + long priority = System.currentTimeMillis(); + + ComponentRepresentation rep = createRep("valid", GeneratedHmacKeyProviderFactory.ID); + rep.setConfig(new MultivaluedHashMap<>()); + rep.getConfig().putSingle("priority", Long.toString(priority)); + + Response response = adminClient.realm("test").components().add(rep); + String id = ApiUtil.getCreatedId(response); + + assertEquals(32, Base64Url.decode(testingClient.testing("test").getComponentConfig(id).getFirst("secret")).length); + + ComponentRepresentation createdRep = adminClient.realm("test").components().component(id).toRepresentation(); + createdRep.getConfig().putSingle("secretSize", "512"); + adminClient.realm("test").components().component(id).update(createdRep); + + assertEquals(512, Base64Url.decode(testingClient.testing("test").getComponentConfig(id).getFirst("secret")).length); + } + + @Test + public void invalidKeysize() throws Exception { + ComponentRepresentation rep = createRep("invalid", GeneratedHmacKeyProviderFactory.ID); + rep.getConfig().putSingle("secretSize", "1234"); + + Response response = adminClient.realm("test").components().add(rep); + assertErrror(response, "'Secret size' should be 32, 64, 128, 256 or 512"); + } + + protected void assertErrror(Response response, String error) { + if (!response.hasEntity()) { + fail("No error message set"); + } + + ErrorRepresentation errorRepresentation = response.readEntity(ErrorRepresentation.class); + assertEquals(error, errorRepresentation.getErrorMessage()); + } + + protected ComponentRepresentation createRep(String name, String providerId) { + ComponentRepresentation rep = new ComponentRepresentation(); + rep.setName(name); + rep.setParentId("test"); + rep.setProviderId(providerId); + rep.setProviderType(KeyProvider.class.getName()); + rep.setConfig(new MultivaluedHashMap<>()); + return rep; + } + +} + diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/RsaGeneratedKeyProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/GeneratedRsaKeyProviderTest.java similarity index 95% rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/RsaGeneratedKeyProviderTest.java rename to testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/GeneratedRsaKeyProviderTest.java index 324d99998e..1f2bcc20e9 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/RsaGeneratedKeyProviderTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/GeneratedRsaKeyProviderTest.java @@ -22,6 +22,7 @@ import org.junit.Rule; import org.junit.Test; import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.PemUtils; +import org.keycloak.jose.jws.AlgorithmType; import org.keycloak.keys.GeneratedRsaKeyProviderFactory; import org.keycloak.keys.KeyMetadata; import org.keycloak.keys.KeyProvider; @@ -45,7 +46,7 @@ import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson; /** * @author Stian Thorgersen */ -public class RsaGeneratedKeyProviderTest extends AbstractKeycloakTest { +public class GeneratedRsaKeyProviderTest extends AbstractKeycloakTest { @Rule public AssertEvents events = new AssertEvents(this); @@ -74,16 +75,15 @@ public class RsaGeneratedKeyProviderTest extends AbstractKeycloakTest { String id = ApiUtil.getCreatedId(response); ComponentRepresentation createdRep = adminClient.realm("test").components().component(id).toRepresentation(); - assertEquals(2, createdRep.getConfig().size()); + assertEquals(1, createdRep.getConfig().size()); assertEquals(Long.toString(priority), createdRep.getConfig().getFirst("priority")); - assertEquals("2048", createdRep.getConfig().getFirst("keySize")); KeysMetadataRepresentation keys = adminClient.realm("test").keys().getKeyMetadata(); KeysMetadataRepresentation.KeyMetadataRepresentation key = keys.getKeys().get(0); assertEquals(id, key.getProviderId()); - assertEquals(KeyMetadata.Type.RSA.name(), key.getType()); + assertEquals(AlgorithmType.RSA.name(), key.getType()); assertEquals(priority, key.getProviderPriority()); assertEquals(2048, ((RSAPublicKey) PemUtils.decodePublicKey(keys.getKeys().get(0).getPublicKey())).getModulus().bitLength()); } @@ -109,7 +109,7 @@ public class RsaGeneratedKeyProviderTest extends AbstractKeycloakTest { KeysMetadataRepresentation.KeyMetadataRepresentation key = keys.getKeys().get(0); assertEquals(id, key.getProviderId()); - assertEquals(KeyMetadata.Type.RSA.name(), key.getType()); + assertEquals(AlgorithmType.RSA.name(), key.getType()); assertEquals(priority, key.getProviderPriority()); assertEquals(4096, ((RSAPublicKey) PemUtils.decodePublicKey(keys.getKeys().get(0).getPublicKey())).getModulus().bitLength()); } @@ -177,7 +177,7 @@ public class RsaGeneratedKeyProviderTest extends AbstractKeycloakTest { rep.getConfig().putSingle("keySize", "1234"); Response response = adminClient.realm("test").components().add(rep); - assertErrror(response, "Keysize should be 1024, 2048 or 4096"); + assertErrror(response, "'Key size' should be 1024, 2048 or 4096"); } protected void assertErrror(Response response, String error) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/RsaKeyProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/ImportedRsaKeyProviderTest.java similarity index 90% rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/RsaKeyProviderTest.java rename to testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/ImportedRsaKeyProviderTest.java index 5bfaef2b2a..cbec6c45b6 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/RsaKeyProviderTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/ImportedRsaKeyProviderTest.java @@ -24,10 +24,11 @@ import org.keycloak.common.util.CertificateUtils; import org.keycloak.common.util.KeyUtils; import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.PemUtils; +import org.keycloak.jose.jws.AlgorithmType; import org.keycloak.keys.Attributes; +import org.keycloak.keys.ImportedRsaKeyProviderFactory; import org.keycloak.keys.KeyMetadata; import org.keycloak.keys.KeyProvider; -import org.keycloak.keys.RsaKeyProviderFactory; import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.idm.ErrorRepresentation; import org.keycloak.representations.idm.KeysMetadataRepresentation; @@ -49,7 +50,7 @@ import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson; /** * @author Stian Thorgersen */ -public class RsaKeyProviderTest extends AbstractKeycloakTest { +public class ImportedRsaKeyProviderTest extends AbstractKeycloakTest { @Rule public AssertEvents events = new AssertEvents(this); @@ -73,7 +74,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest { KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048); String kid = KeyUtils.createKeyId(keyPair.getPublic()); - ComponentRepresentation rep = createRep("valid", RsaKeyProviderFactory.ID); + ComponentRepresentation rep = createRep("valid", ImportedRsaKeyProviderFactory.ID); rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate())); rep.getConfig().putSingle(Attributes.PRIORITY_KEY, Long.toString(priority)); @@ -88,12 +89,12 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest { KeysMetadataRepresentation keys = adminClient.realm("test").keys().getKeyMetadata(); - assertEquals(kid, keys.getActive().get(KeyMetadata.Type.RSA.name())); + assertEquals(kid, keys.getActive().get(AlgorithmType.RSA.name())); KeysMetadataRepresentation.KeyMetadataRepresentation key = keys.getKeys().get(0); assertEquals(id, key.getProviderId()); - assertEquals(KeyMetadata.Type.RSA.name(), key.getType()); + assertEquals(AlgorithmType.RSA.name(), key.getType()); assertEquals(priority, key.getProviderPriority()); assertEquals(kid, key.getKid()); assertEquals(PemUtils.encodeKey(keyPair.getPublic()), keys.getKeys().get(0).getPublicKey()); @@ -108,7 +109,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest { Certificate certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, "test"); String certificatePem = PemUtils.encodeCertificate(certificate); - ComponentRepresentation rep = createRep("valid", RsaKeyProviderFactory.ID); + ComponentRepresentation rep = createRep("valid", ImportedRsaKeyProviderFactory.ID); rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate())); rep.getConfig().putSingle(Attributes.CERTIFICATE_KEY, certificatePem); rep.getConfig().putSingle(Attributes.PRIORITY_KEY, Long.toString(priority)); @@ -130,7 +131,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest { public void invalidPriority() throws Exception { KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048); - ComponentRepresentation rep = createRep("invalid", RsaKeyProviderFactory.ID); + ComponentRepresentation rep = createRep("invalid", ImportedRsaKeyProviderFactory.ID); rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate())); rep.getConfig().putSingle(Attributes.PRIORITY_KEY, "invalid"); @@ -142,7 +143,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest { public void invalidEnabled() throws Exception { KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048); - ComponentRepresentation rep = createRep("invalid", RsaKeyProviderFactory.ID); + ComponentRepresentation rep = createRep("invalid", ImportedRsaKeyProviderFactory.ID); rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate())); rep.getConfig().putSingle(Attributes.ENABLED_KEY, "invalid"); @@ -154,7 +155,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest { public void invalidActive() throws Exception { KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048); - ComponentRepresentation rep = createRep("invalid", RsaKeyProviderFactory.ID); + ComponentRepresentation rep = createRep("invalid", ImportedRsaKeyProviderFactory.ID); rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate())); rep.getConfig().putSingle(Attributes.ACTIVE_KEY, "invalid"); @@ -166,7 +167,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest { public void invalidPrivateKey() throws Exception { KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048); - ComponentRepresentation rep = createRep("invalid", RsaKeyProviderFactory.ID); + ComponentRepresentation rep = createRep("invalid", ImportedRsaKeyProviderFactory.ID); Response response = adminClient.realm("test").components().add(rep); assertErrror(response, "'Private RSA Key' is required"); @@ -185,7 +186,7 @@ public class RsaKeyProviderTest extends AbstractKeycloakTest { KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048); Certificate invalidCertificate = CertificateUtils.generateV1SelfSignedCertificate(KeyUtils.generateRsaKeyPair(2048), "test"); - ComponentRepresentation rep = createRep("invalid", RsaKeyProviderFactory.ID); + ComponentRepresentation rep = createRep("invalid", ImportedRsaKeyProviderFactory.ID); rep.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, PemUtils.encodeKey(keyPair.getPrivate())); rep.getConfig().putSingle(Attributes.CERTIFICATE_KEY, "nonsense"); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/JavaKeystoreKeyProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/JavaKeystoreKeyProviderTest.java index 642a872176..b6fec3e2f8 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/JavaKeystoreKeyProviderTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/JavaKeystoreKeyProviderTest.java @@ -23,6 +23,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.jose.jws.AlgorithmType; import org.keycloak.keys.JavaKeystoreKeyProviderFactory; import org.keycloak.keys.KeyMetadata; import org.keycloak.keys.KeyProvider; @@ -103,7 +104,7 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest { KeysMetadataRepresentation.KeyMetadataRepresentation key = keys.getKeys().get(0); assertEquals(id, key.getProviderId()); - assertEquals(KeyMetadata.Type.RSA.name(), key.getType()); + assertEquals(AlgorithmType.RSA.name(), key.getType()); assertEquals(priority, key.getProviderPriority()); assertEquals(PUBLIC_KEY, key.getPublicKey()); assertEquals(CERTIFICATE, key.getCertificate()); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/KeyRotationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/KeyRotationTest.java index 964832cac0..6c54d2b66e 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/KeyRotationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/KeyRotationTest.java @@ -28,14 +28,13 @@ import org.keycloak.common.util.KeyUtils; import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.PemUtils; import org.keycloak.keys.Attributes; +import org.keycloak.keys.GeneratedHmacKeyProviderFactory; import org.keycloak.keys.KeyProvider; -import org.keycloak.keys.RsaKeyProviderFactory; -import org.keycloak.representations.UserInfo; +import org.keycloak.keys.ImportedRsaKeyProviderFactory; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.idm.KeysMetadataRepresentation; import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.storage.UserStorageProvider; import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.pages.AppPage; @@ -237,7 +236,7 @@ public class KeyRotationTest extends AbstractKeycloakTest { ComponentRepresentation rep = new ComponentRepresentation(); rep.setName("mycomponent"); rep.setParentId("test"); - rep.setProviderId(RsaKeyProviderFactory.ID); + rep.setProviderId(ImportedRsaKeyProviderFactory.ID); rep.setProviderType(KeyProvider.class.getName()); org.keycloak.common.util.MultivaluedHashMap config = new org.keycloak.common.util.MultivaluedHashMap(); @@ -247,6 +246,18 @@ public class KeyRotationTest extends AbstractKeycloakTest { adminClient.realm("test").components().add(rep); + rep = new ComponentRepresentation(); + rep.setName("mycomponent2"); + rep.setParentId("test"); + rep.setProviderId(GeneratedHmacKeyProviderFactory.ID); + rep.setProviderType(KeyProvider.class.getName()); + + config = new org.keycloak.common.util.MultivaluedHashMap(); + config.addFirst("priority", priority); + rep.setConfig(config); + + adminClient.realm("test").components().add(rep); + return publicKey; } @@ -259,13 +270,16 @@ public class KeyRotationTest extends AbstractKeycloakTest { } private void dropKeys(String priority) { + int r = 0; for (ComponentRepresentation c : adminClient.realm("test").components().query("test", KeyProvider.class.getName())) { if (c.getConfig().getFirst("priority").equals(priority)) { adminClient.realm("test").components().component(c.getId()).remove(); - return; + r++; } } - throw new RuntimeException("Failed to find keys1"); + if (r != 2) { + throw new RuntimeException("Failed to find keys1"); + } } private void assertUserInfo(String token, int expectedStatus) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenDuplicateEmailsNotCleanedUpTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenDuplicateEmailsNotCleanedUpTest.java new file mode 100644 index 0000000000..d49c4b8ae9 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenDuplicateEmailsNotCleanedUpTest.java @@ -0,0 +1,100 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.testsuite.oauth; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.keycloak.OAuth2Constants; +import org.keycloak.representations.AccessToken; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.AssertEvents; +import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson; +import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsername; +import org.keycloak.testsuite.util.OAuthClient; +import org.openqa.selenium.By; + +/** + * @author Slawomir Dabek + */ +public class AccessTokenDuplicateEmailsNotCleanedUpTest extends AbstractKeycloakTest { + + @Rule + public AssertEvents events = new AssertEvents(this); + + @Override + public void beforeAbstractKeycloakTest() throws Exception { + super.beforeAbstractKeycloakTest(); + } + + @Before + public void clientConfiguration() { + oauth.clientId("test-app"); + oauth.realm("test-duplicate-emails"); + + RealmRepresentation realmRep = new RealmRepresentation(); + // change realm settings to allow login with email after having imported users with duplicate email addresses + realmRep.setLoginWithEmailAllowed(true); + adminClient.realm("test-duplicate-emails").update(realmRep); + } + + @Override + public void addTestRealms(List testRealms) { + RealmRepresentation realm = loadJson(getClass().getResourceAsStream("/testrealm-duplicate-emails.json"), RealmRepresentation.class); + testRealms.add(realm); + } + + @Test + public void loginWithNonDuplicateEmail() throws Exception { + oauth.doLogin("non-duplicate-email-user@localhost", "password"); + + String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); + OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password"); + + assertEquals(200, response.getStatusCode()); + + AccessToken token = oauth.verifyToken(response.getAccessToken()); + + assertEquals(findUserByUsername(adminClient.realm("test-duplicate-emails"), "non-duplicate-email-user").getId(), token.getSubject()); + } + + @Test + public void loginWithDuplicateEmail() throws Exception { + oauth.doLogin("duplicate-email-user@localhost", "password"); + + assertEquals("Username already exists.", driver.findElement(By.xpath("//span[@class='kc-feedback-text']")).getText()); + } + + @Test + public void loginWithUserHavingDuplicateEmailByUsername() throws Exception { + oauth.doLogin("duplicate-email-user1", "password"); + + String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); + OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password"); + + assertEquals(200, response.getStatusCode()); + + AccessToken token = oauth.verifyToken(response.getAccessToken()); + + assertEquals(findUserByUsername(adminClient.realm("test-duplicate-emails"), "duplicate-email-user1").getId(), token.getSubject()); + assertEquals("duplicate-email-user@localhost", token.getEmail()); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenDuplicateEmailsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenDuplicateEmailsTest.java new file mode 100644 index 0000000000..0f56bcd3f7 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenDuplicateEmailsTest.java @@ -0,0 +1,128 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.testsuite.oauth; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.keycloak.OAuth2Constants; +import org.keycloak.representations.AccessToken; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.AssertEvents; +import org.keycloak.testsuite.util.OAuthClient; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson; +import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsername; +import org.keycloak.testsuite.arquillian.AuthServerTestEnricher; +import org.openqa.selenium.By; + +/** + * @author Slawomir Dabek + */ +public class AccessTokenDuplicateEmailsTest extends AbstractKeycloakTest { + + @Rule + public AssertEvents events = new AssertEvents(this); + + + @Override + public void beforeAbstractKeycloakTest() throws Exception { + super.beforeAbstractKeycloakTest(); + } + + @Before + public void clientConfiguration() { + oauth.clientId("test-app"); + oauth.realm("test-duplicate-emails"); + } + + @Override + public void addTestRealms(List testRealms) { + RealmRepresentation realm = loadJson(getClass().getResourceAsStream("/testrealm-duplicate-emails.json"), RealmRepresentation.class); + testRealms.add(realm); + } + + @Test + public void loginFormUsernameLabel() throws Exception { + oauth.openLoginForm(); + oauth.redirectUri(AuthServerTestEnricher.getAuthServerContextRoot() + "/does/not/matter/"); + + assertEquals("Username", driver.findElement(By.xpath("//label[@for='username']")).getText()); + } + + @Test + public void loginWithNonDuplicateEmailUser() throws Exception { + oauth.doLogin("non-duplicate-email-user", "password"); + + String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); + OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password"); + + assertEquals(200, response.getStatusCode()); + + AccessToken token = oauth.verifyToken(response.getAccessToken()); + + assertEquals(findUserByUsername(adminClient.realm("test-duplicate-emails"), "non-duplicate-email-user").getId(), token.getSubject()); + assertEquals("non-duplicate-email-user@localhost", token.getEmail()); + } + + @Test + public void loginWithFirstDuplicateEmailUser() throws Exception { + oauth.doLogin("duplicate-email-user1", "password"); + + String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); + OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password"); + + assertEquals(200, response.getStatusCode()); + + AccessToken token = oauth.verifyToken(response.getAccessToken()); + + assertEquals(findUserByUsername(adminClient.realm("test-duplicate-emails"), "duplicate-email-user1").getId(), token.getSubject()); + assertEquals("duplicate-email-user@localhost", token.getEmail()); + } + + @Test + public void loginWithSecondDuplicateEmailUser() throws Exception { + oauth.doLogin("duplicate-email-user2", "password"); + + String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); + OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password"); + + assertEquals(200, response.getStatusCode()); + + AccessToken token = oauth.verifyToken(response.getAccessToken()); + + assertEquals(findUserByUsername(adminClient.realm("test-duplicate-emails"), "duplicate-email-user2").getId(), token.getSubject()); + assertEquals("duplicate-email-user@localhost", token.getEmail()); + } + + @Test + public void loginWithNonDuplicateEmail() throws Exception { + oauth.doLogin("non-duplicate-email-user@localhost", "password"); + + assertEquals("Invalid username or password.", driver.findElement(By.xpath("//span[@class='kc-feedback-text']")).getText()); + } + + @Test + public void loginWithDuplicateEmail() throws Exception { + oauth.doLogin("duplicate-email-user@localhost", "password"); + + assertEquals("Invalid username or password.", driver.findElement(By.xpath("//span[@class='kc-feedback-text']")).getText()); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenNoEmailLoginTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenNoEmailLoginTest.java new file mode 100644 index 0000000000..316c09d57e --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenNoEmailLoginTest.java @@ -0,0 +1,83 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.testsuite.oauth; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import org.junit.Before; +import org.junit.Test; +import org.keycloak.OAuth2Constants; +import org.keycloak.representations.AccessToken; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.testsuite.AbstractKeycloakTest; +import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson; +import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsername; +import org.keycloak.testsuite.util.OAuthClient; +import org.openqa.selenium.By; + +/** + * @author Slawomir Dabek + */ +public class AccessTokenNoEmailLoginTest extends AbstractKeycloakTest { + + @Override + public void beforeAbstractKeycloakTest() throws Exception { + super.beforeAbstractKeycloakTest(); + } + + @Before + public void clientConfiguration() { + oauth.clientId("test-app"); + } + + @Override + public void addTestRealms(List testRealms) { + RealmRepresentation realm = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class); + realm.setLoginWithEmailAllowed(false); + testRealms.add(realm); + } + + @Test + public void loginFormUsernameLabel() throws Exception { + oauth.openLoginForm(); + + assertEquals("Username", driver.findElement(By.xpath("//label[@for='username']")).getText()); + } + + @Test + public void loginWithUsername() throws Exception { + oauth.doLogin("non-duplicate-email-user", "password"); + + String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); + OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password"); + + assertEquals(200, response.getStatusCode()); + + AccessToken token = oauth.verifyToken(response.getAccessToken()); + + assertEquals(findUserByUsername(adminClient.realm("test"), "non-duplicate-email-user").getId(), token.getSubject()); + assertEquals("non-duplicate-email-user@localhost", token.getEmail()); + } + + @Test + public void loginWithEmail() throws Exception { + oauth.doLoginGrant("non-duplicate-email-user@localhost", "password"); + + assertEquals("Invalid username or password.", driver.findElement(By.xpath("//span[@class='kc-feedback-text']")).getText()); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java index 7115f7481e..92e68cb046 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java @@ -93,6 +93,7 @@ import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsername; import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsernameId; import static org.keycloak.testsuite.util.OAuthClient.AUTH_SERVER_ROOT; import static org.keycloak.testsuite.util.ProtocolMapperUtil.createRoleNameMapper; +import org.openqa.selenium.By; /** * @author Stian Thorgersen @@ -135,6 +136,13 @@ public class AccessTokenTest extends AbstractKeycloakTest { testRealms.add(realm); } + + @Test + public void loginFormUsernameOrEmailLabel() throws Exception { + oauth.openLoginForm(); + + assertEquals("Username or email", driver.findElement(By.xpath("//label[@for='username']")).getText()); + } @Test public void accessTokenRequest() throws Exception { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OIDCProtocolMappersTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OIDCProtocolMappersTest.java index b896588852..ec2d9f5b79 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OIDCProtocolMappersTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OIDCProtocolMappersTest.java @@ -115,17 +115,18 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { mapper.getConfig().put(AddressMapper.getModelPropertyName(AddressClaimSet.REGION), "region_some"); mapper.getConfig().put(AddressMapper.getModelPropertyName(AddressClaimSet.COUNTRY), "country_some"); mapper.getConfig().remove(AddressMapper.getModelPropertyName(AddressClaimSet.POSTAL_CODE)); // Even if we remove protocolMapper config property, it should still default to postal_code - app.getProtocolMappers().createMapper(mapper); + app.getProtocolMappers().createMapper(mapper).close(); ProtocolMapperRepresentation hard = createHardcodedClaim("hard", "hard", "coded", "String", false, null, true, true); - app.getProtocolMappers().createMapper(hard); - app.getProtocolMappers().createMapper(createHardcodedClaim("hard-nested", "nested.hard", "coded-nested", "String", false, null, true, true)); - app.getProtocolMappers().createMapper(createClaimMapper("custom phone", "phone", "home_phone", "String", true, "", true, true, false)); - app.getProtocolMappers().createMapper(createClaimMapper("nested phone", "phone", "home.phone", "String", true, "", true, true, false)); - app.getProtocolMappers().createMapper(createClaimMapper("departments", "departments", "department", "String", true, "", true, true, true)); - app.getProtocolMappers().createMapper(createHardcodedRole("hard-realm", "hardcoded")); - app.getProtocolMappers().createMapper(createHardcodedRole("hard-app", "app.hardcoded")); - app.getProtocolMappers().createMapper(createRoleNameMapper("rename-app-role", "test-app.customer-user", "realm-user")); + app.getProtocolMappers().createMapper(hard).close(); + app.getProtocolMappers().createMapper(createHardcodedClaim("hard-nested", "nested.hard", "coded-nested", "String", false, null, true, true)).close(); + app.getProtocolMappers().createMapper(createClaimMapper("custom phone", "phone", "home_phone", "String", true, "", true, true, true)).close(); + app.getProtocolMappers().createMapper(createClaimMapper("nested phone", "phone", "home.phone", "String", true, "", true, true, true)).close(); + app.getProtocolMappers().createMapper(createClaimMapper("departments", "departments", "department", "String", true, "", true, true, true)).close(); + app.getProtocolMappers().createMapper(createClaimMapper("firstDepartment", "departments", "firstDepartment", "String", true, "", true, true, false)).close(); + app.getProtocolMappers().createMapper(createHardcodedRole("hard-realm", "hardcoded")).close(); + app.getProtocolMappers().createMapper(createHardcodedRole("hard-app", "app.hardcoded")).close(); + app.getProtocolMappers().createMapper(createRoleNameMapper("rename-app-role", "test-app.customer-user", "realm-user")).close(); } { @@ -147,9 +148,13 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { assertEquals("coded-nested", nested.get("hard")); nested = (Map) idToken.getOtherClaims().get("home"); assertThat((List) nested.get("phone"), hasItems("617-777-6666")); + List departments = (List) idToken.getOtherClaims().get("department"); - assertEquals(2, departments.size()); - assertTrue(departments.contains("finance") && departments.contains("development")); + assertThat(departments, containsInAnyOrder("finance", "development")); + + Object firstDepartment = idToken.getOtherClaims().get("firstDepartment"); + assertThat(firstDepartment, instanceOf(String.class)); + assertThat(firstDepartment, is("finance")); // Has to be the first item AccessToken accessToken = oauth.verifyToken(response.getAccessToken()); assertEquals(accessToken.getName(), "Tom Brady"); @@ -186,6 +191,7 @@ public class OIDCProtocolMappersTest extends AbstractKeycloakTest { || model.getName().equals("hard-nested") || model.getName().equals("custom phone") || model.getName().equals("departments") + || model.getName().equals("firstDepartment") || model.getName().equals("nested phone") || model.getName().equals("rename-app-role") || model.getName().equals("hard-realm") diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/jboss-deployment-structure.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/jboss-deployment-structure.xml index 9a09a89b71..93562268d8 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/jboss-deployment-structure.xml +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/jboss-deployment-structure.xml @@ -18,18 +18,15 @@ + - - - - - + - + \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/cli/kcadm/admin-cli-keystore.jks b/testsuite/integration-arquillian/tests/base/src/test/resources/cli/kcadm/admin-cli-keystore.jks new file mode 100644 index 0000000000..5c789f378f Binary files /dev/null and b/testsuite/integration-arquillian/tests/base/src/test/resources/cli/kcadm/admin-cli-keystore.jks differ diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/org/keycloak/testsuite/admin/client/KEYCLOAK-4040-sharefile-metadata.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/org/keycloak/testsuite/admin/client/KEYCLOAK-4040-sharefile-metadata.xml new file mode 100644 index 0000000000..7fe7824a27 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/org/keycloak/testsuite/admin/client/KEYCLOAK-4040-sharefile-metadata.xml @@ -0,0 +1,33 @@ + + + + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + + + + + + + + + + + + + + Ldu0001hripzSq7zbIMTEKnQCOU= + + + J6IRDZ9RmdtpuwTlEK9HjqtbyeSA2Vz9mXF8yYRLIM0qMxRIvPLiIk02UuzCzEJ1I1xT4pcFuUfrdgDG6r9yf2iS+lV7jd0+DdXTHQ4VbQAZRC3Xd8wJ2RnnbZ3gwbIBBYurnWpKI0OCm0MnGvqV75n5Q6iF5jKA8Y4cFp60HHHnCH4QzpVTV5LjSg91eJA1X+99Xga+sK8Z+ln9wBzsrevz6ZfMt24rOMtb64wfAitz+HiD542Ta2TrzKQTnx+EPcr8xBwC62Gl+lIeE3DwKxtNk8pM8mq42D2b5UVKzjfL+PsYZ8XXBwwnwxFs40uxiI/ivq6KuQ/INt4Z5wmjGw== + + + MIIFUjCCBDqgAwIBAgIDBbH0MA0GCSqGSIb3DQEBBQUAMGExCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEbMBkGA1UEAxMSR2VvVHJ1c3QgRFYgU1NMIENBMB4XDTEyMTIxODAyMjcxOVoXDTE2MTIyMDAwNTYwMFowgckxKTAnBgNVBAUTIHlMbC1HNjFUWWtiaHBaL1JMTnNIYU8vTmJyWEVQOXc1MRMwEQYDVQQLEwpHVDQ4MjA0OTU4MTEwLwYDVQQLEyhTZWUgd3d3Lmdlb3RydXN0LmNvbS9yZXNvdXJjZXMvY3BzIChjKTEyMTcwNQYDVQQLEy5Eb21haW4gQ29udHJvbCBWYWxpZGF0ZWQgLSBRdWlja1NTTChSKSBQcmVtaXVtMRswGQYDVQQDExJzYW1sLnNoYXJlZmlsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPUZyhTw3RmP7Y7v06aHgTNuv/Fm0PbGWbGlZEwqr8TGabocPbnb8iTBWAL2ECXMbx+VrpaHiSOVxqC2Y/vDXOs+1r0CzRKeMC6oQPsXZbieW6HxOAv3UVShxc9nfWI6+immo/o3BYI5WKcOaeZieVlDq7a7ctfSUJXHEBhpaSJNhghb+cUZtp1/EXs8/LyVQ31coo1q726WjCvFVB8OUU2u6BQLcbJF5aG3qh5CkNyivwM3NtNAyHhSXRmwyE+Yv5YNo5QAtUagCGYmS2saEJj8FxhXsNRtfW5B6vVhgmNreTcHCcWTpFGhjvferPjsjaIQAs3P2zx/pW/GSCXHy1AgMBAAGjggGoMIIBpDAfBgNVHSMEGDAWgBSM9NmTCke8AKBKzkt1bqC2sLJ+/DAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdEQQWMBSCEnNhbWwuc2hhcmVmaWxlLmNvbTBBBgNVHR8EOjA4MDagNKAyhjBodHRwOi8vZ3Rzc2xkdi1jcmwuZ2VvdHJ1c3QuY29tL2NybHMvZ3Rzc2xkdi5jcmwwHQYDVR0OBBYEFIDTam2PfOzLpQoclHMwfsUtSi3kMAwGA1UdEwEB/wQCMAAwdQYIKwYBBQUHAQEEaTBnMCwGCCsGAQUFBzABhiBodHRwOi8vZ3Rzc2xkdi1vY3NwLmdlb3RydXN0LmNvbTA3BggrBgEFBQcwAoYraHR0cDovL2d0c3NsZHYtYWlhLmdlb3RydXN0LmNvbS9ndHNzbGR2LmNydDBMBgNVHSAERTBDMEEGCmCGSAGG+EUBBzYwMzAxBggrBgEFBQcCARYlaHR0cDovL3d3dy5nZW90cnVzdC5jb20vcmVzb3VyY2VzL2NwczANBgkqhkiG9w0BAQUFAAOCAQEAU0I6sMe1ZgJ27pdu9qhQLMIgt0w7CuEbLfsSZZdo5TXEj15SGQwU2A0F6o5ivdAvMWTCISJsjHdqCkvB6ZOdMHIfSqA9ARLqX7wLKYfM8X/4RM3koHfqHOvxXBLqCLj2mn34oZrMU5CVI6rqbMoU4D61io7DVswR7Dss0rCh1b1o52ZEBjy5w9oJhRTEFwL7ekf6tR9UioyxQ37pGfD8qOpX1hj5gqcZ5+qUSVNjOjeh+9e9OO5Y/ns3jjHK5ieZPdYeLLOp+D6qzAnOERgvKvkPyRIHZA9tAjxj5KIEzQUopmbP7oH4Ovo6YXT+iIuMVvX3dDu00ExOSZjEeDzo/w== + + + + + ShareFile.com + ShareFile.com, a division of Citrix Systems, Inc + https://www.sharefile.com + + \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm-duplicate-emails.json b/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm-duplicate-emails.json new file mode 100644 index 0000000000..560c1d297d --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm-duplicate-emails.json @@ -0,0 +1,142 @@ +{ + "id": "test-duplicate-emails", + "realm": "test-duplicate-emails", + "enabled": true, + "sslRequired": "external", + "registrationAllowed": true, + "resetPasswordAllowed": true, + "editUsernameAllowed" : true, + "loginWithEmailAllowed": false, + "duplicateEmailsAllowed": true, + "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=", + "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", + "requiredCredentials": [ "password" ], + "defaultRoles": [ "user" ], + "smtpServer": { + "from": "auto@keycloak.org", + "host": "localhost", + "port":"3025" + }, + "users" : [ + { + "username" : "non-duplicate-email-user", + "enabled": true, + "email" : "non-duplicate-email-user@localhost", + "firstName": "Brian", + "lastName": "Cohen", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ], + "realmRoles": ["user", "offline_access"], + "clientRoles": { + "test-app": [ "customer-user" ], + "account": [ "view-profile", "manage-account" ] + } + }, + { + "username" : "duplicate-email-user1", + "enabled": true, + "email" : "duplicate-email-user@localhost", + "firstName": "Agent", + "lastName": "Smith", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ], + "realmRoles": ["user", "offline_access"], + "clientRoles": { + "test-app": [ "customer-user" ], + "account": [ "view-profile", "manage-account" ] + } + }, + { + "username" : "duplicate-email-user2", + "enabled": true, + "email" : "duplicate-email-user@localhost", + "firstName": "Agent", + "lastName": "Smith", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ], + "realmRoles": ["user", "offline_access"], + "clientRoles": { + "test-app": [ "customer-user" ], + "account": [ "view-profile", "manage-account" ] + } + } + ], + "scopeMappings": [ + { + "client": "test-app", + "roles": ["user"] + } + ], + "clients": [ + { + "clientId": "test-app", + "enabled": true, + "baseUrl": "http://localhost:8180/auth/realms/master/app/auth", + "redirectUris": [ + "http://localhost:8180/auth/realms/master/app/auth/*" + ], + "adminUrl": "http://localhost:8180/auth/realms/master/app/admin", + "secret": "password" + } + ], + "roles" : { + "realm" : [ + { + "name": "user", + "description": "Have User privileges" + }, + { + "name": "admin", + "description": "Have Administrator privileges" + }, + { + "name": "customer-user-premium", + "description": "Have User Premium privileges" + }, + { + "name": "sample-realm-role", + "description": "Sample realm role" + } + ], + "client" : { + "test-app" : [ + { + "name": "customer-user", + "description": "Have Customer User privileges" + }, + { + "name": "customer-admin", + "description": "Have Customer Admin privileges" + }, + { + "name": "sample-client-role", + "description": "Sample client role" + }, + { + "name": "customer-admin-composite-role", + "description": "Have Customer Admin privileges via composite role", + "composite" : true, + "composites" : { + "realm" : [ "customer-user-premium" ], + "client" : { + "test-app" : [ "customer-admin" ] + } + } + } + ] + } + + }, + "groups" : [], + "clientScopeMappings": {}, + "internationalizationEnabled": true, + "supportedLocales": ["en", "de"], + "defaultLocale": "en", + "eventsListeners": ["jboss-logging", "event-queue"] +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json index b0e87679b3..969d9b53e2 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json @@ -100,6 +100,22 @@ "clientRoles": { "test-app-scope": [ "test-app-allowed-by-scope", "test-app-disallowed-by-scope" ] } + }, + { + "username" : "non-duplicate-email-user", + "enabled": true, + "email" : "non-duplicate-email-user@localhost", + "firstName": "Brian", + "lastName": "Cohen", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ], + "realmRoles": ["user", "offline_access"], + "clientRoles": { + "test-app": [ "customer-user" ], + "account": [ "view-profile", "manage-account" ] + } } ], "scopeMappings": [ diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/pom.xml index 2726651246..6b26801d70 100644 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/pom.xml +++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/pom.xml @@ -126,6 +126,14 @@ auth-server-remote + + app-server-remote-as7-eap6 + + + remote + ${app.server.management.port.jmx} + + no-offset diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/resources/xslt/arquillian.xsl b/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/resources/xslt/arquillian.xsl index 9da934154e..c18ecc1c64 100644 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/resources/xslt/arquillian.xsl +++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/resources/xslt/arquillian.xsl @@ -32,6 +32,7 @@ org.jboss.as.arquillian.container.remote.RemoteDeployableContainer + ${app.server.management.protocol} ${app.server.host} ${app.server.management.port} admin diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/federation/CreateKerberosUserProvider.java b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/federation/CreateKerberosUserProvider.java index 6347392990..8070a826a4 100644 --- a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/federation/CreateKerberosUserProvider.java +++ b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/federation/CreateKerberosUserProvider.java @@ -13,7 +13,7 @@ public class CreateKerberosUserProvider extends AdminConsoleCreate { private KerberosUserProviderForm form; public CreateKerberosUserProvider() { - setEntity("user-federation"); + setEntity("user-storage"); } @Override diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/federation/CreateLdapUserProvider.java b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/federation/CreateLdapUserProvider.java index 13ba716f63..64272ecda9 100644 --- a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/federation/CreateLdapUserProvider.java +++ b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/federation/CreateLdapUserProvider.java @@ -13,7 +13,7 @@ public class CreateLdapUserProvider extends AdminConsoleCreate { private LdapUserProviderForm form; public CreateLdapUserProvider() { - setEntity("user-federation"); + setEntity("user-storage"); } @Override diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/federation/KerberosUserProviderForm.java b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/federation/KerberosUserProviderForm.java index 1b815f08c1..d0db464b5b 100644 --- a/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/federation/KerberosUserProviderForm.java +++ b/testsuite/integration-arquillian/tests/other/console/src/main/java/org/keycloak/testsuite/console/page/federation/KerberosUserProviderForm.java @@ -54,7 +54,6 @@ public class KerberosUserProviderForm extends Form { } public void setKerberosRealmInput(String kerberosRealm) { - waitUntilElement(By.id("kerberosRealm")).is().present(); setInputValue(kerberosRealmInput, kerberosRealm); } @@ -75,7 +74,6 @@ public class KerberosUserProviderForm extends Form { } public void selectEditMode(String mode) { - waitUntilElement(By.id("editMode")).is().present(); editModeSelect.selectByVisibleText(mode); } diff --git a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/AbstractConsoleTest.java b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/AbstractConsoleTest.java index 8f29a3f06e..ecf69e51f0 100644 --- a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/AbstractConsoleTest.java +++ b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/AbstractConsoleTest.java @@ -107,12 +107,12 @@ public abstract class AbstractConsoleTest extends AbstractAuthTest { } public void assertAlertSuccess() { - assertTrue(alert.isSuccess()); + assertTrue("Alert is not success", alert.isSuccess()); alert.close(); } public void assertAlertDanger() { - assertTrue(alert.isDanger()); + assertTrue("Alert is not danger", alert.isDanger()); alert.close(); } diff --git a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/federation/KerberosUserFederationTest.java b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/federation/KerberosUserFederationTest.java index 79ea899e9a..55e98ffad1 100644 --- a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/federation/KerberosUserFederationTest.java +++ b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/federation/KerberosUserFederationTest.java @@ -2,6 +2,7 @@ package org.keycloak.testsuite.console.federation; import org.jboss.arquillian.graphene.page.Page; import org.junit.Test; +import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.UserFederationProviderRepresentation; import org.keycloak.testsuite.console.AbstractConsoleTest; @@ -34,8 +35,9 @@ public class KerberosUserFederationTest extends AbstractConsoleTest { createKerberosUserProvider.form().setUpdateProfileFirstLogin(true); createKerberosUserProvider.form().save(); assertAlertSuccess(); - RealmRepresentation realm = testRealmResource().toRepresentation(); - UserFederationProviderRepresentation ufpr = realm.getUserFederationProviders().get(0); + + ComponentRepresentation ufpr = testRealmResource().components() + .query(null, "org.keycloak.storage.UserStorageProvider").get(0); assertKerberosSetings(ufpr, "KEYCLOAK.ORG", "HTTP/localhost@KEYCLOAK.ORG", "http.keytab", "true", "true", "true"); } @@ -64,12 +66,12 @@ public class KerberosUserFederationTest extends AbstractConsoleTest { assertAlertSuccess(); } - private void assertKerberosSetings(UserFederationProviderRepresentation ufpr, String kerberosRealm, String serverPrincipal, String keyTab, String debug, String useKerberosForPasswordAuthentication, String updateProfileFirstLogin) { - assertEquals(kerberosRealm, ufpr.getConfig().get("kerberosRealm")); - assertEquals(serverPrincipal, ufpr.getConfig().get("serverPrincipal")); - assertEquals(keyTab, ufpr.getConfig().get("keyTab")); - assertEquals(debug, ufpr.getConfig().get("debug")); - assertEquals(useKerberosForPasswordAuthentication, ufpr.getConfig().get("allowKerberosAuthentication")); - assertEquals(updateProfileFirstLogin, ufpr.getConfig().get("updateProfileFirstLogin")); + private void assertKerberosSetings(ComponentRepresentation ufpr, String kerberosRealm, String serverPrincipal, String keyTab, String debug, String useKerberosForPasswordAuthentication, String updateProfileFirstLogin) { + assertEquals(kerberosRealm, ufpr.getConfig().get("kerberosRealm").get(0)); + assertEquals(serverPrincipal, ufpr.getConfig().get("serverPrincipal").get(0)); + assertEquals(keyTab, ufpr.getConfig().get("keyTab").get(0)); + assertEquals(debug, ufpr.getConfig().get("debug").get(0)); + assertEquals(useKerberosForPasswordAuthentication, ufpr.getConfig().get("allowPasswordAuthentication").get(0)); + assertEquals(updateProfileFirstLogin, ufpr.getConfig().get("updateProfileFirstLogin").get(0)); } } \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/federation/LdapUserFederationTest.java b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/federation/LdapUserFederationTest.java index 2d405eabc2..2f4fb40ee5 100644 --- a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/federation/LdapUserFederationTest.java +++ b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/federation/LdapUserFederationTest.java @@ -3,8 +3,7 @@ package org.keycloak.testsuite.console.federation; import org.apache.commons.configuration.ConfigurationException; import org.jboss.arquillian.graphene.page.Page; import org.junit.Test; -import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.representations.idm.UserFederationProviderRepresentation; +import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.testsuite.console.AbstractConsoleTest; import org.keycloak.testsuite.console.page.federation.CreateLdapUserProvider; import org.keycloak.util.ldap.LDAPEmbeddedServer; @@ -53,12 +52,13 @@ public class LdapUserFederationTest extends AbstractConsoleTest { createLdapUserProvider.form().save(); assertAlertSuccess(); - RealmRepresentation realm = testRealmResource().toRepresentation(); - UserFederationProviderRepresentation ufpr = realm.getUserFederationProviders().get(0); - assertLdapProviderSetting(ufpr, "ldap", 0, WRITABLE, "false", "ad", "1", "true", "true", "false"); + ComponentRepresentation ufpr = testRealmResource().components() + .query(null, "org.keycloak.storage.UserStorageProvider").get(0); + + assertLdapProviderSetting(ufpr, "ldap", "0", WRITABLE, "false", "ad", "1", "true", "true", "false"); assertLdapBasicMapping(ufpr, "cn", "cn", "objectGUID", "person, organizationalPerson, user", "ou=People,dc=keycloak,dc=org"); - assertLdapSyncSetings(ufpr, "1000", 0, 0); + assertLdapSyncSetings(ufpr, "1000", "-1", "-1"); assertLdapKerberosSetings(ufpr, "KEYCLOAK.ORG", "HTTP/localhost@KEYCLOAK.ORG", "http.keytab", "true", "false"); } @@ -75,12 +75,13 @@ public class LdapUserFederationTest extends AbstractConsoleTest { createLdapUserProvider.form().save(); assertAlertSuccess(); - RealmRepresentation realm = testRealmResource().toRepresentation(); - UserFederationProviderRepresentation ufpr = realm.getUserFederationProviders().get(0); - assertLdapProviderSetting(ufpr, "ldap", 0, READ_ONLY, "false", "rhds", "1", "true", "true", "true"); + ComponentRepresentation ufpr = testRealmResource().components() + .query(null, "org.keycloak.storage.UserStorageProvider").get(0); + + assertLdapProviderSetting(ufpr, "ldap", "0", READ_ONLY, "false", "rhds", "1", "true", "true", "true"); assertLdapBasicMapping(ufpr, "uid", "uid", "nsuniqueid", "inetOrgPerson, organizationalPerson", "ou=People,dc=keycloak,dc=org"); - assertLdapSyncSetings(ufpr, "1000", 0, 0); + assertLdapSyncSetings(ufpr, "1000", "-1", "-1"); } @Test @@ -175,44 +176,44 @@ public class LdapUserFederationTest extends AbstractConsoleTest { assertTrue("Vendors list doesn't match", vendorsExpected.containsAll(vendorsActual)); } - private void assertLdapProviderSetting(UserFederationProviderRepresentation ufpr, String name, int priority, + private void assertLdapProviderSetting(ComponentRepresentation ufpr, String name, String priority, String editMode, String syncRegistrations, String vendor, String searchScope, String connectionPooling, String pagination, String enableAccountAfterPasswordUpdate) { - assertEquals(name, ufpr.getDisplayName()); - assertEquals(priority, ufpr.getPriority()); - assertEquals(editMode, ufpr.getConfig().get("editMode")); - assertEquals(syncRegistrations, ufpr.getConfig().get("syncRegistrations")); - assertEquals(vendor, ufpr.getConfig().get("vendor")); - assertEquals(searchScope, ufpr.getConfig().get("searchScope")); - assertEquals(connectionPooling, ufpr.getConfig().get("connectionPooling")); - assertEquals(pagination, ufpr.getConfig().get("pagination")); + assertEquals(name, ufpr.getName()); + assertEquals(priority, ufpr.getConfig().get("priority").get(0)); + assertEquals(editMode, ufpr.getConfig().get("editMode").get(0)); + assertEquals(syncRegistrations, ufpr.getConfig().get("syncRegistrations").get(0)); + assertEquals(vendor, ufpr.getConfig().get("vendor").get(0)); + assertEquals(searchScope, ufpr.getConfig().get("searchScope").get(0)); + assertEquals(connectionPooling, ufpr.getConfig().get("connectionPooling").get(0)); + assertEquals(pagination, ufpr.getConfig().get("pagination").get(0)); // assertEquals(enableAccountAfterPasswordUpdate, ufpr.getConfig().get("userAccountControlsAfterPasswordUpdate")); } - private void assertLdapBasicMapping(UserFederationProviderRepresentation ufpr, String usernameLdapAttribute, + private void assertLdapBasicMapping(ComponentRepresentation ufpr, String usernameLdapAttribute, String rdnLdapAttr, String uuidLdapAttr, String userObjectClasses, String userDN) { - assertEquals(usernameLdapAttribute, ufpr.getConfig().get("usernameLDAPAttribute")); - assertEquals(rdnLdapAttr, ufpr.getConfig().get("rdnLDAPAttribute")); - assertEquals(uuidLdapAttr, ufpr.getConfig().get("uuidLDAPAttribute")); - assertEquals(userObjectClasses, ufpr.getConfig().get("userObjectClasses")); - assertEquals(userDN, ufpr.getConfig().get("usersDn")); + assertEquals(usernameLdapAttribute, ufpr.getConfig().get("usernameLDAPAttribute").get(0)); + assertEquals(rdnLdapAttr, ufpr.getConfig().get("rdnLDAPAttribute").get(0)); + assertEquals(uuidLdapAttr, ufpr.getConfig().get("uuidLDAPAttribute").get(0)); + assertEquals(userObjectClasses, ufpr.getConfig().get("userObjectClasses").get(0)); + assertEquals(userDN, ufpr.getConfig().get("usersDn").get(0)); } - private void assertLdapKerberosSetings(UserFederationProviderRepresentation ufpr, String kerberosRealm, + private void assertLdapKerberosSetings(ComponentRepresentation ufpr, String kerberosRealm, String serverPrincipal, String keyTab, String debug, String useKerberosForPasswordAuthentication) { - assertEquals(kerberosRealm, ufpr.getConfig().get("kerberosRealm")); - assertEquals(serverPrincipal, ufpr.getConfig().get("serverPrincipal")); - assertEquals(keyTab, ufpr.getConfig().get("keyTab")); - assertEquals(debug, ufpr.getConfig().get("debug")); + assertEquals(kerberosRealm, ufpr.getConfig().get("kerberosRealm").get(0)); + assertEquals(serverPrincipal, ufpr.getConfig().get("serverPrincipal").get(0)); + assertEquals(keyTab, ufpr.getConfig().get("keyTab").get(0)); + assertEquals(debug, ufpr.getConfig().get("debug").get(0)); assertEquals(useKerberosForPasswordAuthentication, - ufpr.getConfig().get("useKerberosForPasswordAuthentication")); + ufpr.getConfig().get("useKerberosForPasswordAuthentication").get(0)); } - private void assertLdapSyncSetings(UserFederationProviderRepresentation ufpr, String batchSize, - int periodicFullSync, int periodicChangedUsersSync) { - assertEquals(batchSize, ufpr.getConfig().get("batchSizeForSync")); - assertEquals(periodicFullSync, ufpr.getFullSyncPeriod()); - assertEquals(periodicChangedUsersSync, ufpr.getChangedSyncPeriod()); + private void assertLdapSyncSetings(ComponentRepresentation ufpr, String batchSize, + String periodicFullSync, String periodicChangedUsersSync) { + assertEquals(batchSize, ufpr.getConfig().get("batchSizeForSync").get(0)); + assertEquals(periodicFullSync, ufpr.getConfig().get("fullSyncPeriod").get(0)); + assertEquals(periodicChangedUsersSync, ufpr.getConfig().get("changedSyncPeriod").get(0)); } private LDAPEmbeddedServer startEmbeddedLdapServer() throws Exception { diff --git a/testsuite/integration-arquillian/tests/other/nodejs_adapter/src/test/java/org/keycloak/testsuite/adapter/nodejs/NodejsAdapterTest.java b/testsuite/integration-arquillian/tests/other/nodejs_adapter/src/test/java/org/keycloak/testsuite/adapter/nodejs/NodejsAdapterTest.java index 1a1fa14bdd..0df2186ef5 100644 --- a/testsuite/integration-arquillian/tests/other/nodejs_adapter/src/test/java/org/keycloak/testsuite/adapter/nodejs/NodejsAdapterTest.java +++ b/testsuite/integration-arquillian/tests/other/nodejs_adapter/src/test/java/org/keycloak/testsuite/adapter/nodejs/NodejsAdapterTest.java @@ -146,7 +146,6 @@ public class NodejsAdapterTest extends AbstractAuthTest { // KEYCLOAK-3284 @Test - @Ignore // to be enabled when KEYCLOAK-3284 is fixed public void sessionTest() { nodejsExamplePage.clickLogin(); testRealmLoginPage.form().login(USER, PASSWORD); diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml index 131b7cc337..1c29c824e0 100755 --- a/testsuite/integration/pom.xml +++ b/testsuite/integration/pom.xml @@ -591,32 +591,5 @@ - - - - - ldap.vendor - msad - - - msad - - - - org.apache.maven.plugins - maven-surefire-plugin - - - org/keycloak/testsuite/federation/ldap/base/** - - - **/LDAPMultipleAttributesTest.java - - - - - - - diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java index 404d24dd67..1f2f620f6a 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java @@ -72,6 +72,12 @@ public class AdapterTest { .name("product-portal").contextPath("/product-portal") .servletClass(ProductServlet.class).adapterConfigPath(url.getPath()) .role("user").deployApplication(); + + url = getClass().getResource("/adapter-test/product-autodetect-bearer-only-keycloak.json"); + createApplicationDeployment() + .name("product-portal-autodetect-bearer-only").contextPath("/product-portal-autodetect-bearer-only") + .servletClass(ProductServlet.class).adapterConfigPath(url.getPath()) + .role("user").deployApplication(); // Test that replacing system properties works for adapters System.setProperty("app.server.base.url", "http://localhost:8081"); @@ -149,6 +155,11 @@ public class AdapterTest { testStrategy.testNullBearerTokenCustomErrorPage(); } + @Test + public void testAutodetectBearerOnly() throws Exception { + testStrategy.testAutodetectBearerOnly(); + } + @Test public void testBasicAuthErrorHandling() throws Exception { testStrategy.testBasicAuthErrorHandling(); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java index 9341df2d2d..bd0a144607 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java @@ -400,6 +400,55 @@ public class AdapterTestStrategy extends ExternalResource { Time.setOffset(0); } + public void testAutodetectBearerOnly() throws Exception { + Client client = ClientBuilder.newClient(); + + // Do not redirect client to login page if it's an XHR + WebTarget target = client.target(APP_SERVER_BASE_URL + "/product-portal-autodetect-bearer-only"); + Response response = target.request().header("X-Requested-With", "XMLHttpRequest").get(); + Assert.assertEquals(401, response.getStatus()); + response.close(); + + // Do not redirect client to login page if it's a partial Faces request + response = target.request().header("Faces-Request", "partial/ajax").get(); + Assert.assertEquals(401, response.getStatus()); + response.close(); + + // Do not redirect client to login page if it's a SOAP request + response = target.request().header("SOAPAction", "").get(); + Assert.assertEquals(401, response.getStatus()); + response.close(); + + // Do not redirect client to login page if Accept header is missing + response = target.request().get(); + Assert.assertEquals(401, response.getStatus()); + response.close(); + + // Do not redirect client to login page if client does not understand HTML reponses + response = target.request().header(HttpHeaders.ACCEPT, "application/json,text/xml").get(); + Assert.assertEquals(401, response.getStatus()); + response.close(); + + // Redirect client to login page if it's not an XHR + response = target.request().header("X-Requested-With", "Dont-Know").header(HttpHeaders.ACCEPT, "*/*").get(); + Assert.assertEquals(302, response.getStatus()); + Assert.assertTrue(response.getHeaderString(HttpHeaders.LOCATION).contains("response_type=code")); + response.close(); + + // Redirect client to login page if client explicitely understands HTML responses + response = target.request().header(HttpHeaders.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9").get(); + Assert.assertEquals(302, response.getStatus()); + Assert.assertTrue(response.getHeaderString(HttpHeaders.LOCATION).contains("response_type=code")); + response.close(); + + // Redirect client to login page if client understands all response types + response = target.request().header(HttpHeaders.ACCEPT, "*/*").get(); + Assert.assertEquals(302, response.getStatus()); + Assert.assertTrue(response.getHeaderString(HttpHeaders.LOCATION).contains("response_type=code")); + response.close(); + client.close(); + } + /** * KEYCLOAK-518 * @throws Exception diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/AbstractPhotozAdminTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/AbstractPhotozAdminTest.java index c9b69d8bff..c46b338957 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/AbstractPhotozAdminTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/AbstractPhotozAdminTest.java @@ -101,7 +101,7 @@ public abstract class AbstractPhotozAdminTest extends AbstractAuthorizationTest // during tests we create resource instances, but we need to reload them to get their collections updated List updatedPermissions = permissions.stream().map(permission -> { - Resource resource = storeFactory.getResourceStore().findById(permission.getResource().getId()); + Resource resource = storeFactory.getResourceStore().findById(permission.getResource().getId(), resourceServer.getId()); return new ResourcePermission(resource, permission.getScopes(), permission.getResourceServer()); }).collect(Collectors.toList()); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/ResourceManagementTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/ResourceManagementTest.java index 086b5ac94c..4a4fc9a4ad 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/ResourceManagementTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/ResourceManagementTest.java @@ -53,7 +53,7 @@ public class ResourceManagementTest extends AbstractPhotozAdminTest { ResourceRepresentation resource = response.readEntity(ResourceRepresentation.class); onAuthorizationSession(authorizationProvider -> { - Resource resourceModel = authorizationProvider.getStoreFactory().getResourceStore().findById(resource.getId()); + Resource resourceModel = authorizationProvider.getStoreFactory().getResourceStore().findById(resource.getId(), resourceServer.getId()); assertNotNull(resourceModel); assertEquals(resource.getId(), resourceModel.getId()); @@ -89,7 +89,7 @@ public class ResourceManagementTest extends AbstractPhotozAdminTest { ResourceRepresentation resource = response.readEntity(ResourceRepresentation.class); onAuthorizationSession(authorizationProvider -> { - Resource resourceModel = authorizationProvider.getStoreFactory().getResourceStore().findById(resource.getId()); + Resource resourceModel = authorizationProvider.getStoreFactory().getResourceStore().findById(resource.getId(), resourceServer.getId()); assertNotNull(resourceModel); assertEquals(resource.getId(), resourceModel.getId()); @@ -147,7 +147,7 @@ public class ResourceManagementTest extends AbstractPhotozAdminTest { assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus()); onAuthorizationSession(authorizationProvider -> { - Resource resourceModel = authorizationProvider.getStoreFactory().getResourceStore().findById(resource.getId()); + Resource resourceModel = authorizationProvider.getStoreFactory().getResourceStore().findById(resource.getId(), resourceServer.getId()); assertNull(resourceModel); }); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/ResourcePermissionManagementTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/ResourcePermissionManagementTest.java index 9ecbc3d992..4708a2a2c8 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/ResourcePermissionManagementTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/ResourcePermissionManagementTest.java @@ -46,8 +46,12 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -77,7 +81,7 @@ public class ResourcePermissionManagementTest extends AbstractPhotozAdminTest { PolicyRepresentation permission = response.readEntity(PolicyRepresentation.class); onAuthorizationSession(authorizationProvider -> { - Policy policyModel = authorizationProvider.getStoreFactory().getPolicyStore().findById(permission.getId()); + Policy policyModel = authorizationProvider.getStoreFactory().getPolicyStore().findById(permission.getId(), resourceServer.getId()); assertNotNull(policyModel); assertEquals(permission.getId(), policyModel.getId()); @@ -357,7 +361,7 @@ public class ResourcePermissionManagementTest extends AbstractPhotozAdminTest { PolicyRepresentation permission = response.readEntity(PolicyRepresentation.class); onAuthorizationSession(authorizationProvider -> { - Policy policyModel = authorizationProvider.getStoreFactory().getPolicyStore().findById(permission.getId()); + Policy policyModel = authorizationProvider.getStoreFactory().getPolicyStore().findById(permission.getId(), resourceServer.getId()); assertNotNull(policyModel); assertEquals(permission.getId(), policyModel.getId()); @@ -430,7 +434,8 @@ public class ResourcePermissionManagementTest extends AbstractPhotozAdminTest { config.put("defaultResourceType", albumResource.getType()); - String applyPolicies = JsonSerialization.writeValueAsString(new String[]{this.anyUserPolicy.getId(), this.administrationPolicy.getId()}); + String[] associatedPolicies = {this.anyUserPolicy.getId(), this.administrationPolicy.getId()}; + String applyPolicies = JsonSerialization.writeValueAsString(associatedPolicies); config.put("applyPolicies", applyPolicies); @@ -443,14 +448,15 @@ public class ResourcePermissionManagementTest extends AbstractPhotozAdminTest { PolicyRepresentation permission = response.readEntity(PolicyRepresentation.class); onAuthorizationSession(authorizationProvider -> { - Policy policyModel = authorizationProvider.getStoreFactory().getPolicyStore().findById(permission.getId()); + Policy policyModel = authorizationProvider.getStoreFactory().getPolicyStore().findById(permission.getId(), resourceServer.getId()); assertNotNull(policyModel); assertEquals(permission.getId(), policyModel.getId()); assertEquals(permission.getName(), policyModel.getName()); assertEquals(permission.getType(), policyModel.getType()); assertTrue(permission.getConfig().containsValue(albumResource.getType())); - assertTrue(permission.getConfig().containsValue(applyPolicies)); + assertTrue(policyModel.getAssociatedPolicies().stream().map(Policy::getId).collect(Collectors.toList()).containsAll(Arrays.asList(associatedPolicies))); + assertEquals(resourceServer.getId(), policyModel.getResourceServer().getId()); }); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/ScopeManagementTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/ScopeManagementTest.java index b2e1a421ea..644883ff9c 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/ScopeManagementTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/ScopeManagementTest.java @@ -57,7 +57,7 @@ public class ScopeManagementTest extends AbstractPhotozAdminTest { ScopeRepresentation scope = response.readEntity(ScopeRepresentation.class); onAuthorizationSession(authorizationProvider -> { - Scope scopeModel = authorizationProvider.getStoreFactory().getScopeStore().findById(scope.getId()); + Scope scopeModel = authorizationProvider.getStoreFactory().getScopeStore().findById(scope.getId(), resourceServer.getId()); assertNotNull(scopeModel); assertEquals(scope.getId(), scopeModel.getId()); @@ -86,7 +86,7 @@ public class ScopeManagementTest extends AbstractPhotozAdminTest { ScopeRepresentation scope = response.readEntity(ScopeRepresentation.class); onAuthorizationSession(authorizationProvider -> { - Scope scopeModel = authorizationProvider.getStoreFactory().getScopeStore().findById(scope.getId()); + Scope scopeModel = authorizationProvider.getStoreFactory().getScopeStore().findById(scope.getId(), resourceServer.getId()); assertNotNull(scopeModel); assertEquals(scope.getId(), scopeModel.getId()); @@ -138,7 +138,7 @@ public class ScopeManagementTest extends AbstractPhotozAdminTest { assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus()); onAuthorizationSession(authorizationProvider -> { - Scope scopeModel = authorizationProvider.getStoreFactory().getScopeStore().findById(scope.getId()); + Scope scopeModel = authorizationProvider.getStoreFactory().getScopeStore().findById(scope.getId(), resourceServer.getId()); assertNull(scopeModel); }); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPBinaryAttributesTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPBinaryAttributesTest.java new file mode 100644 index 0000000000..1707aef0cc --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPBinaryAttributesTest.java @@ -0,0 +1,287 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.testsuite.federation.storage.ldap; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import javax.ws.rs.ClientErrorException; +import javax.ws.rs.core.Response; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; +import org.junit.runners.MethodSorters; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.component.ComponentModel; +import org.keycloak.component.ComponentValidationException; +import org.keycloak.models.Constants; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.LDAPConstants; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.utils.UserModelDelegate; +import org.keycloak.representations.idm.ComponentRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.services.managers.RealmManager; +import org.keycloak.storage.UserStorageProvider; +import org.keycloak.storage.UserStorageProviderModel; +import org.keycloak.storage.ldap.LDAPStorageProvider; +import org.keycloak.storage.ldap.LDAPStorageProviderFactory; +import org.keycloak.storage.ldap.LDAPUtils; +import org.keycloak.storage.ldap.idm.model.LDAPObject; +import org.keycloak.storage.ldap.mappers.LDAPStorageMapper; +import org.keycloak.storage.ldap.mappers.UserAttributeLDAPStorageMapper; +import org.keycloak.testsuite.rule.KeycloakRule; +import org.keycloak.testsuite.rule.LDAPRule; + +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MASTER; +import static org.keycloak.models.AdminRoles.ADMIN; +import static org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT; + +/** + * @author Marek Posolda + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class LDAPBinaryAttributesTest { + + private static LDAPRule ldapRule = new LDAPRule(); + + private static ComponentModel ldapModel = null; + + + private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() { + + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { + MultivaluedHashMap ldapConfig = LDAPTestUtils.getLdapRuleConfig(ldapRule); + ldapConfig.putSingle(LDAPConstants.SYNC_REGISTRATIONS, "true"); + ldapConfig.putSingle(LDAPConstants.EDIT_MODE, UserStorageProvider.EditMode.WRITABLE.toString()); + UserStorageProviderModel model = new UserStorageProviderModel(); + model.setLastSync(0); + model.setChangedSyncPeriod(-1); + model.setFullSyncPeriod(-1); + model.setName("test-ldap"); + model.setPriority(0); + model.setProviderId(LDAPStorageProviderFactory.PROVIDER_NAME); + model.setConfig(ldapConfig); + + ldapModel = appRealm.addComponentModel(model); + LDAPTestUtils.addZipCodeLDAPMapper(appRealm, ldapModel); + + // Delete all LDAP users and add some new for testing + LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + LDAPTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm); + +// LDAPObject john = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234"); +// LDAPTestUtils.updateLDAPPassword(ldapFedProvider, john, "Password1"); +// +// LDAPObject existing = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "existing", "Existing", "Foo", "existing@email.org", null, "5678"); + + appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true); + } + }); + + @ClassRule + public static TestRule chain = RuleChain + .outerRule(ldapRule) + .around(keycloakRule); + + private static final String JPEG_PHOTO_BASE64 = "/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAMDAwMDAwQEBAQFBQUFBQcHBgYHBwsICQgJCAsRCwwLCwwLEQ8SDw4PEg8bFRMTFRsfGhkaHyYiIiYwLTA+PlQBAwMDAwMDBAQEBAUFBQUFBwcGBgcHCwgJCAkICxELDAsLDAsRDxIPDg8SDxsVExMVGx8aGRofJiIiJjAtMD4+VP/CABEIAFIAWAMBIgACEQEDEQH/xAAdAAACAgMBAQEAAAAAAAAAAAAGCAUHAwQJAgAB/9oACAEBAAAAAElmzK1aOaraUmpiktrD10DayAIMkKunPQdk+hrZwkUNkMrM88VDtt7r7C1KCJtprbSBP2J6K+VDqUlwErkDnOm/HF9IPDaWQ+cP85sXS7OCj1Iybjj2zVvJA0b91BqJ1JuQDYfkuSWrGdFzsMsWkaIUGHh4IuY1glqtWrK8G89YeJk+ldfT1pHz9//EABoBAAIDAQEAAAAAAAAAAAAAAAUHAwYIBAD/2gAIAQIQAAAApKICefJOj5haleI6vyhC1+FEa9UydaFaD7hDLtU9jttBsCJni6f/xAAaAQADAQEBAQAAAAAAAAAAAAAFBgcDBAII/9oACAEDEAAAAF5+Mdc4X6LUuz2kyGquiYYI/PzvUctR8d5289ghjS48fuOK/wD/xAA5EAABAwIEBAMGAwcFAAAAAAABAgMEABEFBhIhEzFBURQiMgdCYXGBwSOhsRUkM3KCkdFSU2J04f/aAAgBAQABPwGHLbkIVbmjmPhSF60BSL2VUPDhLYLjL4KuotyP0NQPZq5iWBmUiWPGFarIV/DIHS/MGpeC4lhrnh5MdxLgJBuna/wPWsFyW2/kVaH2/wB7dK5LSyPMDbb6KtWD+zqBKypKTIZQJ89q4dPNG10f+1GydizuAPSSgstwlHiawQVnVby/LrTrfBOkqF6gyWmEJQSoKvfVYUiawVbrubc6bQEr4qSkKpDj/iEMKSdLoDYSm/XkRWSco+Bg4dORxo8lCyJcd1NgqxI68tjSG2Wm/wAFKUi97DYVi2GRcZh8F+9goLSR0IpC02TakqA2rE4acSgPw9WgPJ0qPYE71mL2fQJLanYiOGmJh5DKEep125IualtvxJDrDo0OtLKFjsQd6U8tJ2686deDa/Ii1/STvWU8KwDOOVoaZ8VPioV2VLQdC0WNwQRSU2QEk3sNyetPExzq9zr8KEwpulXTrTWJx0OLQp5IOskC/Sm5iFpSpCr3A/S9IeF9zQJPI2r2g5Tw3AWFS0uPOPzpay3q9KE8zc9Tvzpxv6HrWUcCh5hnmC/iQiv6bskp18XvbcVlXJE3K0xbrOJJfadFnGi2U/UG5peocqxPEmYrCtak3I2FTcckBwp1q9PIck87Hbn3oLllYWTvzvUbFpkE7XIv1+FYFizOIM60W18inqmmeIrnXtTaxmW82p2I61CYFkL2Ui6upI5XrhKS0nWL6VadQ95J7VkzKcZzEor4lsHgkL4KwSrWO1ulJdWbeUHbcjaluqAPkX+VZjxMLkur07Nk7cjYdjyvTSzJcceWLFe4+9Mt6x5eQNOBCiR8enSsqylxcSLSdR1g7X273pmVYedJT9Kx7L+G5mjBp911sp9KkqOx+KTsazDl+Rl3EBHcktvNKuptSFbEfEdCKyEGHNi+OKhZUGFNg/1JJ3BoSU99/lTuzZUs2FutZhaSZT5SggJVcG29r9bU1p8OhQuRalyHgBa4TyriWTfqayjEck4kp1CiA0j63VyplTqPKtQV89qnfsl5jhzyzw1/7igP7XrPOF4fgmMNrg4ozIjSFH8BCwtbI7E9u1ZCwvCpjrcyRKQHW3PLG1WJ+N/8U65o3sBSWCs8R3c9B2rN2BGWy7ISQDpsRb86SuRC1IlNLQb9ep+B60ZUbYnaokOXijwaYQdJ5q90bd6wDBF4JESlJC7+Zdhvc00W3htWbZmHYVhi3J8PxUVR0rb06t+l+3zrFXMLU6VQUvJJcWVBQ0pSOiUi6jt3Jpryea52r2c+KxKap9995TEVvyjWbFajtfvWsL+VKW1iBJQoKZbV03uof4qThEWWyriNJWee++9R8uYTH06YrYIH+kU9BaCNKU2t6aw93iJ0KO6axfFIOFSW1OSEsFwn1GwJHOsdbYzhlybEgy21ugC4QoG+nfSfnU6MAtY9JR7vfferqPQmsOzTOw9bDMdTLZTfhtcklZFrnufnWLe0HM+JLMbi+FSfIpDW3zuedZXQzGyxh/IDw4Wf6/Mo1h8pqbCaktnyOi6T8L1PxlqFj0OE5sJLK7fzDkKOlbYVWM4yzl7FY63dmJKTqI90o62r2pQXnDExpg8aK4gNmytkHoR8DTMx6MVLYdW2u1uo/NNL1JSt3UFq08qCVN+pR2pS9SiR32oyPElp824iLIc/5dlUjOstvLyMKCAAEFBdJ9y/Kms2QMNyvBjofC3ww2opQb8/MAfvWPY/IxzFvFqUW9B/BsfSByFSfaGpzAm+E4WZrTyF2725/Q9qx7NT+ZHY7i20t8JrTpBvc33NSJkzgmKH3THJ1cLUdN/lQTtvUSO2664FXA0LWr6C9KJJNConrP8AIaRzXTPo/t+tO/f71J9X0H6U1T1L+1QNncQ/6n3Ff//EACQQAQEAAgICAQQDAQAAAAAAAAERACExQVGBYXGRobEQwfDR/9oACAEBAAE/EFxCJHa+QcvGUwhESkN9fiYHv0VpOm0o9OFKc/OP0nyMqyNS7O0x2FTnAH6HHF+oMkQP6cPbVCpqdHusdzBsTjjKJKrAZwN411hOmhN3Q+OdYFicQWN6QTJzrwEHC7aJwbcdwysS/AW7GNhVpxF5hwesFHQhHh77IyY3UAIHQHgwuHXP36x4apQUBQcWcXJmcwJakbelduaVYQKsBS3eAVJyB+cQnvn1w6o9ZDI6pnUIiPfdxG9iCBQRXreNy18f2PjFCeB0fH3MMVGwtGz1hMB2JKPunWGBO9cLh1omlDTJZj6GlAPSm3RMCgLAgSfWedY3okyFyEYQWneraZLkQPZNfjHeyDpVdBP7dYuNwjIibANNDHmgaDAeXRptkBMDpGVE6Hqjvltx4aCVkJq9o4gOA4y6pRz7bXiMrUKoBArI5ZdacH0tDvKBFDsjg3oRL8G/tceEiW1Y6UAXW0jxK47AXAL0gOg3HkF1MeStC2JfnHWoCStw/wDe804AlIhCOmmMAu7jZ+Fw0B2U6H2hTGAuDJHQLuYV1xK4W0jhBExMKEcCV+5m01yqkMRn8VrZVUN24oNBOeYF1eI6cEGRpvfJrB+GQLVd9ZIRJROhHod4eCYNA8PTGhag8kNPJivYOADUtVxNVsC44dbHxjhJe2AYOXuc16+cQODu1ej3TF3gwCyreivI5JUksX4fesSG1rUxOwRWcCuJY2pBRrL0AQyrY62UfZjjgyEKuhQpIYLK9dtHzfVC4V2Gib2vOsI+EqqQI4gFwY1Py185FplUDmj8LU84jKWQGhYb+GYoKQdBVa8fLgkXPHI8s+uWSNh+pzh6/EJ6CF0MduPBMS2w88OjhgEW0VUC+Ht6xbrYBeevOWtwmrpdV6RSGDx83G9WqMDRe3oFCj2uUoN4FoGLoFWobz7guMGnj2W4jCzVWD1RwXq8VTsWxxafUEV2R2WOBoHSEnGyczAFEcmv43hrndLdMA4GT4cJ8xi4mEFtpKDrTMB0WxjaWEKwJpfaeX95xkQjKxHJ5FgWWx4WjvAwAztJmx6tsUyaoEOUrgVl3jsSC8V1lgV0fwbzc/pzg9R17zkOp+jGh6Z5/wDGuf2M4nvOf+9ZxOos/wD/xAAvEQABAwMCBQMDAwUAAAAAAAACAQMEAAUREiEGBxMxQSJRcSMzYRQygRUXQpGy/9oACAECAQE/AHIDLhl0i0+VQvHvXF3MCTaZ7kW0voatv+o1FP8ABcKBfhavPM+dcbZESOCsSQki49pzj0dk+Fqy81r3FkSBkfUbkzAdQjVS6I9lQUqw8SWzi6GRRjVSHTrBdiHPaisAEuUNa5mXmBDgLFC4lBubGHmCRDFT8YQsYwtQmHpkpBFOorpepPfPmoPAIq0RPvCCGKYRKv3B71pZV8fqte6ePmuVEmaxxMLDUkWmDTL45RNenslA5lNiT/aVzPmzJt7EHzJ1GQwKKONOd65cxYz9wdUw3ANqfaOQaiC6UHbHxVxYaGzTlfX6aNL37ZqG49CurT0UnWzE8obKay/hPNWtXpdqhSFdV7qsoqmrStqq+cj4WuYPB5SVfu7twJWmAXDKhnT7COKslxnW6cMtvIg0qIW22Pav7i2hwOq7EdF3VujZJiuI+MnrzDVuO30owEmtvuq/lVrh+2G1dbZLfbkDBceDW4iEmlM99SVFVhY7e6l6e5KhLTtmjXAFafQTbwuyjsua454JC4223QLXHajNhJQnhBNO2MZ/NWLl0/Luk39Q2rcQeu2Cki6s9gJK4e5ZXNqUSzxT9KXWbcHOC2T0qlcPWz+k2dmC+YPi1kQXTjLefShJ7pTUiO2pAaIqpjG2MJQft/irl+9Ka+4XwtJ9qmu4/NF99z4D/mv/xAAsEQACAQMCBAUDBQAAAAAAAAABAgMABBEFEgYhMUETFCIycVFSYRUzNGJy/9oACAEDAQE/AE09bmQ+F8kNWhcIsrs9wAYmHp+vMVYcIx291cO+JInQqoPXJ61c8MWzW6Rx5HhoQP7HHKtZ0W5tMJMMhjkN8V5B/uFcM2EodZDAjxMNpY9qtoRyIxtHUVLNawYyBQEM6+nGcVxVFB5F96bmHsI7ZokZ61wdEP0pJcDdJzPqzSnZESprWricTAktWgtNtBfJJ7GtRtYZonWRAQQeXatdsXtr90VNoxyAbIrhDXbaJYrKO1IZurA5yfqajYk7GwAwqWxikOSVNJFHbx5BBP4q9uikEojdC/hnCEg1LNHM5Zwuc9xWnRywSq8UrJjmDitP4ha2neS6kYnwiFB+7NXvFFtawxEOrTPtJUdgetapxVBDZFrdwZPSy5/PUGtS1iO7n8xEGQuPUuT1qEhgx25yerVb9Kv/AHU/8l/8VcftrSe5fmovYK//2Q=="; + + + + protected Keycloak adminClient; + + + @Before + public void before() { + adminClient = Keycloak.getInstance(AUTH_SERVER_ROOT, MASTER, ADMIN, ADMIN, Constants.ADMIN_CLI_CLIENT_ID); + } + + @After + public void after() { + ComponentRepresentation jpegMapper = adminClient.realm("test").components().query(ldapModel.getId(), LDAPStorageMapper.class.getName(), "jpeg-mapper").get(0); + adminClient.realm("test").components().component(jpegMapper.getId()).remove(); + + adminClient.close(); + } + + + // Test invalid mapper configuration - validation exception thrown + @Test + public void test01InvalidMapperConfiguration() { + KeycloakSession session = keycloakRule.startSession(); + try { + RealmManager manager = new RealmManager(session); + RealmModel appRealm = manager.getRealm("test"); + + ComponentModel ldapComponentMapper = LDAPTestUtils.addUserAttributeMapper(appRealm, ldapModel, "jpeg-mapper", LDAPConstants.JPEG_PHOTO, LDAPConstants.JPEG_PHOTO); + + ldapComponentMapper.put(UserAttributeLDAPStorageMapper.IS_BINARY_ATTRIBUTE, true); + try { + appRealm.updateComponent(ldapComponentMapper); + Assert.fail("Not expected to successfully update mapper"); + } catch (ComponentValidationException cve) { + // Expected + } + + } finally { + keycloakRule.stopSession(session, true); + } + } + + + @Test + public void test02ReadOnly() { + String mapperId = addPhotoMapper(); + + KeycloakSession session = keycloakRule.startSession(); + try { + RealmManager manager = new RealmManager(session); + RealmModel appRealm = manager.getRealm("test"); + + // Add user directly to LDAP + LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + addLDAPUser(ldapFedProvider, appRealm, "johnphoto", "John", "Photo", "john@photo.org", JPEG_PHOTO_BASE64); + + // Set mapper to be read-only + ComponentModel ldapComponentMapper = appRealm.getComponent(mapperId); + ldapComponentMapper.put(UserAttributeLDAPStorageMapper.READ_ONLY, true); + appRealm.updateComponent(ldapComponentMapper); + } finally { + keycloakRule.stopSession(session, true); + } + + // Assert john found + getUserAndAssertPhoto("johnphoto", true); + } + + + @Test + public void test03WritableMapper() { + String mapperId = addPhotoMapper(); + + // Create user joe with jpegPHoto + UserRepresentation joe = new UserRepresentation(); + joe.setUsername("joephoto"); + joe.setEmail("joe@photo.org"); + joe.setAttributes(Collections.singletonMap(LDAPConstants.JPEG_PHOTO, Arrays.asList(JPEG_PHOTO_BASE64))); + Response response = adminClient.realm("test").users().create(joe); + response.close(); + + + // Assert he is found including jpegPhoto + joe = getUserAndAssertPhoto("joephoto", true); + + + // Try to update him with some big non-LDAP mapped attribute. It will fail + try { + joe.getAttributes().put("someOtherPhoto", Arrays.asList(JPEG_PHOTO_BASE64)); + adminClient.realm("test").users().get(joe.getId()).update(joe); + Assert.fail("Not expected to successfully update user"); + } catch (ClientErrorException cee) { + // Expected + } + + // Remove jpegPhoto attribute and assert it was successfully removed + joe.getAttributes().remove("someOtherPhoto"); + joe.getAttributes().remove(LDAPConstants.JPEG_PHOTO); + adminClient.realm("test").users().get(joe.getId()).update(joe); + getUserAndAssertPhoto("joephoto", false); + } + + + private String addPhotoMapper() { + KeycloakSession session = keycloakRule.startSession(); + try { + RealmManager manager = new RealmManager(session); + RealmModel appRealm = manager.getRealm("test"); + + ComponentModel ldapComponentMapper = LDAPTestUtils.addUserAttributeMapper(appRealm, ldapModel, "jpeg-mapper", LDAPConstants.JPEG_PHOTO, LDAPConstants.JPEG_PHOTO); + + ldapComponentMapper.put(UserAttributeLDAPStorageMapper.IS_BINARY_ATTRIBUTE, true); + ldapComponentMapper.put(UserAttributeLDAPStorageMapper.ALWAYS_READ_VALUE_FROM_LDAP, true); + appRealm.updateComponent(ldapComponentMapper); + return ldapComponentMapper.getId(); + } finally { + keycloakRule.stopSession(session, true); + } + } + + + private LDAPObject addLDAPUser(LDAPStorageProvider ldapProvider, RealmModel realm, final String username, + final String firstName, final String lastName, final String email, String jpegPhoto) { + UserModel helperUser = new UserModelDelegate(null) { + + @Override + public String getUsername() { + return username; + } + + @Override + public String getFirstName() { + return firstName; + } + + @Override + public String getLastName() { + return lastName; + } + + @Override + public String getEmail() { + return email; + } + + @Override + public List getAttribute(String name) { + if (LDAPConstants.JPEG_PHOTO.equals(name)) { + return Arrays.asList(jpegPhoto); + } else { + return Collections.emptyList(); + } + } + }; + return LDAPUtils.addUserToLDAP(ldapProvider, realm, helperUser); + } + + + private UserRepresentation getUserAndAssertPhoto(String username, boolean isPhotoExpected) { + List johns = adminClient.realm("test").users().search(username, 0, 10); + Assert.assertEquals(1, johns.size()); + UserRepresentation john = johns.get(0); + Assert.assertEquals(username, john.getUsername()); + Assert.assertTrue(john.getAttributes().containsKey(LDAPConstants.LDAP_ID)); // Doublecheck it's the LDAP mapped user + + if (isPhotoExpected) { + Assert.assertEquals(JPEG_PHOTO_BASE64, john.getAttributes().get(LDAPConstants.JPEG_PHOTO).get(0)); + } else { + Assert.assertFalse(john.getAttributes().containsKey(LDAPConstants.JPEG_PHOTO)); + } + return john; + } + +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapperSyncTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapperSyncTest.java index 29e79c45fd..746b3021ee 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapperSyncTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapperSyncTest.java @@ -104,8 +104,8 @@ public class LDAPGroupMapperSyncTest { LDAPObject group11 = LDAPTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group11"); LDAPObject group12 = LDAPTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group12", descriptionAttrName, "group12 - description"); - LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, group1, group11, false); - LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, group1, group12, true); + LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, "not-used", group1, group11, false); + LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, "not-used", group1, group12, true); } }); @@ -144,7 +144,7 @@ public class LDAPGroupMapperSyncTest { // Add recursive group mapping to LDAP. Check that sync with preserve group inheritance will fail LDAPObject group1 = groupMapper.loadLDAPGroupByName("group1"); LDAPObject group12 = groupMapper.loadLDAPGroupByName("group12"); - LDAPUtils.addMember(ldapProvider, MembershipType.DN, LDAPConstants.MEMBER, group12, group1, true); + LDAPUtils.addMember(ldapProvider, MembershipType.DN, LDAPConstants.MEMBER, "not-used", group12, group1, true); try { new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromFederationProviderToKeycloak(realm); @@ -171,7 +171,7 @@ public class LDAPGroupMapperSyncTest { Assert.assertEquals("group12 - description", kcGroup12.getFirstAttribute(descriptionAttrName)); // Cleanup - remove recursive mapping in LDAP - LDAPUtils.deleteMember(ldapProvider, MembershipType.DN, LDAPConstants.MEMBER, group12, group1); + LDAPUtils.deleteMember(ldapProvider, MembershipType.DN, LDAPConstants.MEMBER, "not-used", group12, group1); } finally { keycloakRule.stopSession(session, false); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapperTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapperTest.java index 022a0760f2..e36f8e855d 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapperTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPGroupMapperTest.java @@ -111,8 +111,8 @@ public class LDAPGroupMapperTest { LDAPObject group12 = LDAPTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group12", descriptionAttrName, "group12 - description"); LDAPObject groupSpecialCharacters = LDAPTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group-spec,ia*l_characžter)s", descriptionAttrName, "group-special-characters"); - LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, group1, group11, false); - LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, group1, group12, true); + LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, "not-used", group1, group11, false); + LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, "not-used", group1, group12, true); // Sync LDAP groups to Keycloak DB ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "groupsMapper"); @@ -366,14 +366,14 @@ public class LDAPGroupMapperTest { // 2 - Add one existing user rob to LDAP group LDAPObject jamesLdap = ldapProvider.loadLDAPUserByUsername(appRealm, "jameskeycloak"); - LDAPUtils.addMember(ldapProvider, MembershipType.DN, LDAPConstants.MEMBER, group2, jamesLdap, false); + LDAPUtils.addMember(ldapProvider, MembershipType.DN, LDAPConstants.MEMBER, "not-used", group2, jamesLdap, false); // 3 - Add non-existing user to LDAP group LDAPDn nonExistentDn = LDAPDn.fromString(ldapProvider.getLdapIdentityStore().getConfig().getUsersDn()); nonExistentDn.addFirst(jamesLdap.getRdnAttributeName(), "nonexistent"); LDAPObject nonExistentLdapUser = new LDAPObject(); nonExistentLdapUser.setDn(nonExistentDn); - LDAPUtils.addMember(ldapProvider, MembershipType.DN, LDAPConstants.MEMBER, group2, nonExistentLdapUser, true); + LDAPUtils.addMember(ldapProvider, MembershipType.DN, LDAPConstants.MEMBER, "not-used", group2, nonExistentLdapUser, true); // 4 - Check group members. Just existing user rob should be present groupMapper.syncDataFromFederationProviderToKeycloak(appRealm); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPLegacyImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPLegacyImportTest.java index 9e26fcfe9f..a3d9178732 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPLegacyImportTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPLegacyImportTest.java @@ -69,6 +69,7 @@ import org.openqa.selenium.WebDriver; import java.io.IOException; import java.util.List; +import java.util.Map; import static org.junit.Assert.assertEquals; @@ -80,7 +81,11 @@ import static org.junit.Assert.assertEquals; @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class LDAPLegacyImportTest { - private static LDAPRule ldapRule = new LDAPRule(); + // This test is executed just for the embedded LDAP server + private static LDAPRule ldapRule = new LDAPRule((Map ldapConfig) -> { + String connectionURL = ldapConfig.get(LDAPConstants.CONNECTION_URL); + return !"ldap://localhost:10389".equals(connectionURL); + }); private static ComponentModel ldapModel = null; diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPMultipleAttributesTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPMultipleAttributesTest.java index 7de1ae9962..dce0f60d64 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPMultipleAttributesTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPMultipleAttributesTest.java @@ -56,6 +56,7 @@ import java.util.Arrays; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; +import java.util.Map; /** * @author Marek Posolda @@ -66,7 +67,15 @@ public class LDAPMultipleAttributesTest { protected String APP_SERVER_BASE_URL = "http://localhost:8081"; protected String LOGIN_URL = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri(APP_SERVER_BASE_URL + "/auth")).build("test").toString(); - private static LDAPRule ldapRule = new LDAPRule(); + + // Skip this test on MSAD due to lack of supported user multivalued attributes + private static LDAPRule ldapRule = new LDAPRule((Map ldapConfig) -> { + + String vendor = ldapConfig.get(LDAPConstants.VENDOR); + return (vendor.equals(LDAPConstants.VENDOR_ACTIVE_DIRECTORY)); + + }); + private static ComponentModel ldapModel = null; diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/SimplePerfTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/SimplePerfTest.java new file mode 100644 index 0000000000..a63b1e03ce --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/SimplePerfTest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.testsuite.model; + +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.keycloak.common.util.KeyUtils; +import org.keycloak.jose.jws.JWSBuilder; +import org.keycloak.protocol.RestartLoginCookie; +import org.keycloak.testsuite.rule.WebResource; +import org.keycloak.testsuite.rule.WebRule; +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import java.security.KeyPair; + +import static org.junit.Assert.assertEquals; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +@Ignore +public class SimplePerfTest { + + @Rule + public WebRule webRule = new WebRule(this); + + @WebResource + public WebDriver driver; + + public static final String PORT = "8080"; + public static final int WARMUP = 1000; + public static final int COUNT = 10000; + + @Test + public void simplePerf() { + // Warm-up + for (int i = 0; i < WARMUP; i++) { + doLoginLogout(PORT); + } + + long start = System.currentTimeMillis(); + long s = start; + + for (int i = 0; i < COUNT; i++) { + doLoginLogout(PORT); + + if (i % 100 == 0) { + System.out.println(i + " " + (System.currentTimeMillis() - s) + " ms"); + } + s = System.currentTimeMillis(); + } + + System.out.println(""); + System.out.println("Average: " + ((System.currentTimeMillis() - start) / COUNT) + " ms"); + System.out.println("Total: " + ((System.currentTimeMillis() - start)) + " ms"); + + } + + private void doLoginLogout(String port) { + driver.navigate().to("http://localhost:" + port + "/auth/realms/master/account/"); + + driver.findElement(By.id("username")).sendKeys("admin"); + driver.findElement(By.id("password")).sendKeys("admin"); + driver.findElement(By.name("login")).click(); + + assertEquals("http://localhost:" + port + "/auth/realms/master/account/", driver.getCurrentUrl()); + + driver.findElement(By.linkText("Sign Out")).click(); + } + +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KerberosRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KerberosRule.java index 8d46513ac5..38a1df20a6 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KerberosRule.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KerberosRule.java @@ -36,6 +36,11 @@ public class KerberosRule extends LDAPRule { private final String configLocation; public KerberosRule(String configLocation) { + this(configLocation, null); + } + + public KerberosRule(String configLocation, LDAPRuleCondition condition) { + super(condition); this.configLocation = configLocation; // Global kerberos configuration diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/LDAPRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/LDAPRule.java index 4bf0e4c449..6dbc938ec2 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/LDAPRule.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/LDAPRule.java @@ -17,7 +17,10 @@ package org.keycloak.testsuite.rule; -import org.junit.rules.ExternalResource; +import org.jboss.logging.Logger; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; import org.keycloak.testsuite.federation.ldap.LDAPTestConfiguration; import org.keycloak.util.ldap.LDAPEmbeddedServer; @@ -25,28 +28,76 @@ import java.util.Map; import java.util.Properties; /** + * This rule handles: + * - Reading of LDAP configuration from properties file + * - Eventually start+stop of LDAP embedded server. + * - Eventually allows to ignore the test if particular condition is not met. This allows to run specific tests just for some LDAP vendors + * * @author Marek Posolda */ -public class LDAPRule extends ExternalResource { +public class LDAPRule implements TestRule { + + private static final Logger logger = Logger.getLogger(LDAPRule.class); public static final String LDAP_CONNECTION_PROPERTIES_LOCATION = "ldap/ldap-connection.properties"; protected LDAPTestConfiguration ldapTestConfiguration; protected LDAPEmbeddedServer ldapEmbeddedServer; + private final LDAPRuleCondition condition; + + + public LDAPRule() { + this(null); + } + + public LDAPRule(LDAPRuleCondition condition) { + this.condition = condition; + } + + @Override - protected void before() throws Throwable { + public Statement apply(Statement base, Description description) { + return new Statement() { + + @Override + public void evaluate() throws Throwable { + boolean skipTest = before(); + + if (skipTest) { + logger.infof("Skip %s due to LDAPRuleCondition not met", description.getDisplayName()); + return; + } + + try { + base.evaluate(); + } finally { + after(); + } + } + }; + } + + + // Return true if test should be skipped + protected boolean before() throws Throwable { String connectionPropsLocation = getConnectionPropertiesLocation(); ldapTestConfiguration = LDAPTestConfiguration.readConfiguration(connectionPropsLocation); + if (condition != null && condition.skipTest(ldapTestConfiguration.getLDAPConfig())) { + return true; + } + if (ldapTestConfiguration.isStartEmbeddedLdapLerver()) { ldapEmbeddedServer = createServer(); ldapEmbeddedServer.init(); ldapEmbeddedServer.start(); } + + return false; } - @Override + protected void after() { try { if (ldapEmbeddedServer != null) { @@ -78,4 +129,12 @@ public class LDAPRule extends ExternalResource { public int getSleepTime() { return ldapTestConfiguration.getSleepTime(); } + + + // Allows to skip particular LDAP test just under specific conditions (eg. some test running just on Active Directory) + public interface LDAPRuleCondition { + + boolean skipTest(Map ldapConfig); + + } } diff --git a/testsuite/integration/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration/src/test/resources/META-INF/keycloak-server.json index 3f4ddd1a1e..27e9f5ec9e 100755 --- a/testsuite/integration/src/test/resources/META-INF/keycloak-server.json +++ b/testsuite/integration/src/test/resources/META-INF/keycloak-server.json @@ -64,7 +64,7 @@ "connectionsJpa": { "default": { - "url": "${keycloak.connectionsJpa.url:jdbc:h2:mem:test}", + "url": "${keycloak.connectionsJpa.url:jdbc:h2:mem:test;DB_CLOSE_DELAY=-1}", "driver": "${keycloak.connectionsJpa.driver:org.h2.Driver}", "driverDialect": "${keycloak.connectionsJpa.driverDialect:}", "user": "${keycloak.connectionsJpa.user:sa}", diff --git a/testsuite/integration/src/test/resources/adapter-test/product-autodetect-bearer-only-keycloak.json b/testsuite/integration/src/test/resources/adapter-test/product-autodetect-bearer-only-keycloak.json new file mode 100644 index 0000000000..b92abc6dc4 --- /dev/null +++ b/testsuite/integration/src/test/resources/adapter-test/product-autodetect-bearer-only-keycloak.json @@ -0,0 +1,11 @@ +{ + "realm" : "demo", + "resource" : "product-portal", + "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", + "auth-server-url" : "http://localhost:8081/auth", + "ssl-required" : "external", + "credentials" : { + "secret": "password" + }, + "autodetect-bearer-only" : true +} diff --git a/testsuite/integration/src/test/resources/log4j.properties b/testsuite/integration/src/test/resources/log4j.properties index 2fa1d70fbc..4cc1f91c42 100755 --- a/testsuite/integration/src/test/resources/log4j.properties +++ b/testsuite/integration/src/test/resources/log4j.properties @@ -58,7 +58,10 @@ log4j.logger.org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory=${ log4j.logger.org.keycloak.connections.jpa.HibernateStatsReporter=debug # Enable to view ldap logging -# log4j.logger.org.keycloak.federation.ldap=trace +# log4j.logger.org.keycloak.storage.ldap=trace + +# Enable to view queries to LDAP +# log4j.logger.org.keycloak.storage.ldap.idm.store.ldap.LDAPIdentityStore=trace # Enable to view kerberos/spnego logging # log4j.logger.org.keycloak.federation.kerberos=trace diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index 173bcfbd91..0106590b45 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -32,6 +32,10 @@ resetPasswordAllowed=Forgot password resetPasswordAllowed.tooltip=Show a link on login page for user to click on when they have forgotten their credentials. rememberMe=Remember Me rememberMe.tooltip=Show checkbox on login page to allow user to remain logged in between browser restarts until session expires. +loginWithEmailAllowed=Login with email +loginWithEmailAllowed.tooltip=Allow users to log in with their email address. +duplicateEmailsAllowed=Duplicate emails +duplicateEmailsAllowed.tooltip=Allow multiple users to have the same email address. Changing this setting will also clear the users cache. It is recommended to manually update email constraints of existing users in the database after switching off support for duplicate email addresses. verifyEmail=Verify email verifyEmail.tooltip=Require the user to verify their email address the first time they login. sslRequired=Require SSL @@ -39,6 +43,7 @@ sslRequired.option.all=all requests sslRequired.option.external=external requests sslRequired.option.none=none sslRequired.tooltip=Is HTTPS required? 'None' means HTTPS is not required for any client IP address. 'External requests' means localhost and private IP addresses can access without HTTPS. 'All requests' means HTTPS is required for all IP addresses. +publicKeys=Public keys publicKey=Public key privateKey=Private key gen-new-keys=Generate new keys @@ -272,6 +277,8 @@ logout-service-post-binding-url=Logout Service POST Binding URL logout-service-post-binding-url.tooltip=SAML POST Binding URL for the client's single logout service. You can leave this blank if you are using a different binding logout-service-redir-binding-url=Logout Service Redirect Binding URL logout-service-redir-binding-url.tooltip=SAML Redirect Binding URL for the client's single logout service. You can leave this blank if you are using a different binding. +saml-signature-keyName-transformer=SAML Signature Key Name +saml-signature-keyName-transformer.tooltip=Signed SAML documents contain identification of signing key in KeyName element. For Keycloak / RH-SSO counterparty, use KEY_ID, for MS AD FS use CERT_SUBJECT, for others check and use NONE if no other option works. # client import import-client=Import Client @@ -891,6 +898,7 @@ include-representation.tooltip=Include JSON representation for create and update clear-admin-events.tooltip=Deletes all admin events in the database. server-version=Server Version server-profile=Server Profile +server-disabled=Server Disabled Features info=Info providers=Providers server-time=Server Time @@ -1029,6 +1037,10 @@ authz-result=Result authz-authorization-services-enabled=Authorization Enabled authz-authorization-services-enabled.tooltip=Enable/Disable fine-grained authorization support for a client authz-required=Required +authz-show-details=Show Details +authz-hide-details=Hide Details +authz-associated-permissions=Associated Permissions +authz-no-permission-associated=No permissions associated # Authz Settings authz-import-config.tooltip=Import a JSON file containing authorization settings for this resource server. @@ -1049,6 +1061,7 @@ authz-export-settings.tooltip=Export and download all authorization settings for authz-no-resources-available=No resources available. authz-no-scopes-assigned=No scopes assigned. authz-no-type-defined=No type defined. +authz-no-uri-defined=No URI defined. authz-no-permission-assigned=No permission assigned. authz-no-policy-assigned=No policy assigned. authz-create-permission=Create permission diff --git a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js index ea039c195a..2a86b93687 100644 --- a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js @@ -86,10 +86,13 @@ module.controller('ResourceServerResourceCtrl', function($scope, $http, $route, $scope.query = { realm: realm.realm, client : client.id, + deep: false, max : 20, first : 0 }; + $scope.listSizes = [5, 10, 20]; + ResourceServer.get({ realm : $route.current.params.realm, client : client.id @@ -124,20 +127,86 @@ module.controller('ResourceServerResourceCtrl', function($scope, $http, $route, $scope.searchQuery = function() { $scope.searchLoaded = false; - $scope.resources = ResourceServerResource.query($scope.query, function() { + ResourceServerResource.query($scope.query, function(response) { $scope.searchLoaded = true; $scope.lastSearch = $scope.query.search; + $scope.resources = response; + if ($scope.detailsFilter) { + $scope.showDetails(); + } }); }; + + $scope.loadDetails = function (resource) { + if (resource.details) { + resource.details.loaded = !resource.details.loaded; + return; + } + + resource.details = {loaded: false}; + + ResourceServerResource.scopes({ + realm : $route.current.params.realm, + client : client.id, + rsrid : resource._id + }, function(response) { + resource.scopes = response; + ResourceServerResource.permissions({ + realm : $route.current.params.realm, + client : client.id, + rsrid : resource._id + }, function(response) { + resource.policies = response; + resource.details.loaded = true; + }); + }); + } + + $scope.showDetails = function(item) { + if (item) { + $scope.loadDetails(item); + } else { + for (i = 0; i < $scope.resources.length; i++) { + $scope.loadDetails($scope.resources[i]); + } + } + }; }); module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $route, $location, realm, ResourceServer, client, ResourceServerResource, ResourceServerScope, AuthzDialog, Notifications) { $scope.realm = realm; $scope.client = client; - ResourceServerScope.query({realm : realm.realm, client : client.id}, function (data) { - $scope.scopes = data; - }); + $scope.scopesUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; + } + $scope.query = { + realm: realm.realm, + client : client.id, + name: query.term.trim(), + deep: false, + max : 20, + first : 0 + }; + ResourceServerScope.query($scope.query, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + return object.name; + }, + formatSelection: function(object, container, query) { + return object.name; + } + }; var $instance = this; @@ -165,6 +234,9 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r }, true); $scope.save = function() { + for (i = 0; i < $scope.resource.scopes.length; i++) { + delete $scope.resource.scopes[i].text; + } $instance.checkNameAvailability(function () { ResourceServerResource.save({realm : realm.realm, client : $scope.client.id}, $scope.resource, function(data) { $location.url("/realms/" + realm.realm + "/clients/" + $scope.client.id + "/authz/resource-server/resource/" + data._id); @@ -186,17 +258,9 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r data.scopes = []; } - if (!data.policies) { - data.policies = []; - } - $scope.resource = angular.copy(data); $scope.changed = false; - for (i = 0; i < $scope.resource.scopes.length; i++) { - $scope.resource.scopes[i] = $scope.resource.scopes[i].name; - } - $scope.originalResource = angular.copy($scope.resource); $scope.$watch('resource', function() { @@ -206,6 +270,9 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r }, true); $scope.save = function() { + for (i = 0; i < $scope.resource.scopes.length; i++) { + delete $scope.resource.scopes[i].text; + } $instance.checkNameAvailability(function () { ResourceServerResource.update({realm : realm.realm, client : $scope.client.id, rsrid : $scope.resource._id}, $scope.resource, function() { $route.reload(); @@ -215,22 +282,28 @@ module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $r } $scope.remove = function() { - var msg = ""; + ResourceServerResource.permissions({ + realm : $route.current.params.realm, + client : client.id, + rsrid : $scope.resource._id + }, function (permissions) { + var msg = ""; - if ($scope.resource.policies.length > 0) { - msg = "

This resource is referenced in some policies:

"; - msg += "
    "; - for (i = 0; i < $scope.resource.policies.length; i++) { - msg+= "
  • " + $scope.resource.policies[i].name + "
  • "; + if (permissions.length > 0 && !$scope.deleteConsent) { + msg = "

    This resource is referenced in some policies:

    "; + msg += "
      "; + for (i = 0; i < permissions.length; i++) { + msg+= "
    • " + permissions[i].name + "
    • "; + } + msg += "
    "; + msg += "

    If you remove this resource, the policies above will be affected and will not be associated with this resource anymore.

    "; } - msg += "
"; - msg += "

If you remove this resource, the policies above will be affected and will not be associated with this resource anymore.

"; - } - AuthzDialog.confirmDeleteWithMsg($scope.resource.name, "Resource", msg, function() { - ResourceServerResource.delete({realm : realm.realm, client : $scope.client.id, rsrid : $scope.resource._id}, null, function() { - $location.url("/realms/" + realm.realm + "/clients/" + $scope.client.id + "/authz/resource-server/resource"); - Notifications.success("The resource has been deleted."); + AuthzDialog.confirmDeleteWithMsg($scope.resource.name, "Resource", msg, function() { + ResourceServerResource.delete({realm : realm.realm, client : $scope.client.id, rsrid : $scope.resource._id}, null, function() { + $location.url("/realms/" + realm.realm + "/clients/" + $scope.client.id + "/authz/resource-server/resource"); + Notifications.success("The resource has been deleted."); + }); }); }); } @@ -269,10 +342,13 @@ module.controller('ResourceServerScopeCtrl', function($scope, $http, $route, $lo $scope.query = { realm: realm.realm, client : client.id, + deep: false, max : 20, first : 0 }; + $scope.listSizes = [5, 10, 20]; + ResourceServer.get({ realm : $route.current.params.realm, client : client.id @@ -304,14 +380,53 @@ module.controller('ResourceServerScopeCtrl', function($scope, $http, $route, $lo $scope.searchQuery(); } - $scope.searchQuery = function() { + $scope.searchQuery = function(detailsFilter) { $scope.searchLoaded = false; - $scope.scopes = ResourceServerScope.query($scope.query, function() { + ResourceServerScope.query($scope.query, function(response) { + $scope.scopes = response; $scope.searchLoaded = true; $scope.lastSearch = $scope.query.search; + if ($scope.detailsFilter) { + $scope.showDetails(); + } }); }; + + $scope.loadDetails = function (scope) { + if (scope.details) { + scope.details.loaded = !scope.details.loaded; + return; + } + + scope.details = {loaded: false}; + + ResourceServerScope.resources({ + realm : $route.current.params.realm, + client : client.id, + id : scope.id + }, function(response) { + scope.resources = response; + ResourceServerScope.permissions({ + realm : $route.current.params.realm, + client : client.id, + id : scope.id + }, function(response) { + scope.policies = response; + scope.details.loaded = true; + }); + }); + } + + $scope.showDetails = function(item) { + if (item) { + $scope.loadDetails(item); + } else { + for (i = 0; i < $scope.scopes.length; i++) { + $scope.loadDetails($scope.scopes[i]); + } + } + }; }); module.controller('ResourceServerScopeDetailCtrl', function($scope, $http, $route, $location, realm, ResourceServer, client, ResourceServerScope, AuthzDialog, Notifications) { @@ -377,22 +492,28 @@ module.controller('ResourceServerScopeDetailCtrl', function($scope, $http, $rout } $scope.remove = function() { - var msg = ""; + ResourceServerScope.permissions({ + realm : $route.current.params.realm, + client : client.id, + id : $scope.scope.id + }, function (permissions) { + var msg = ""; - if ($scope.scope.policies.length > 0) { - msg = "

This resource is referenced in some policies:

"; - msg += "
    "; - for (i = 0; i < $scope.scope.policies.length; i++) { - msg+= "
  • " + $scope.scope.policies[i].name + "
  • "; + if (permissions.length > 0 && !$scope.deleteConsent) { + msg = "

    This scope is referenced in some policies:

    "; + msg += "
      "; + for (i = 0; i < permissions.length; i++) { + msg+= "
    • " + permissions[i].name + "
    • "; + } + msg += "
    "; + msg += "

    If you remove this scope, the policies above will be affected and will not be associated with this scope anymore.

    "; } - msg += "
"; - msg += "

If you remove this resource, the policies above will be affected and will not be associated with this resource anymore.

"; - } - AuthzDialog.confirmDeleteWithMsg($scope.scope.name, "Scope", msg, function() { - ResourceServerScope.delete({realm : realm.realm, client : $scope.client.id, id : $scope.scope.id}, null, function() { - $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/scope"); - Notifications.success("The scope has been deleted."); + AuthzDialog.confirmDeleteWithMsg($scope.scope.name, "Scope", msg, function() { + ResourceServerScope.delete({realm : realm.realm, client : $scope.client.id, id : $scope.scope.id}, null, function() { + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/scope"); + Notifications.success("The scope has been deleted."); + }); }); }); } @@ -432,10 +553,12 @@ module.controller('ResourceServerPolicyCtrl', function($scope, $http, $route, $l realm: realm.realm, client : client.id, permission: false, - max : 20, + max: 20, first : 0 }; + $scope.listSizes = [5, 10, 20]; + PolicyProvider.query({ realm : $route.current.params.realm, client : client.id @@ -481,18 +604,42 @@ module.controller('ResourceServerPolicyCtrl', function($scope, $http, $route, $l $scope.searchLoaded = false; ResourceServerPolicy.query($scope.query, function(data) { - $scope.policies = []; - - for (i = 0; i < data.length; i++) { - if (data[i].type != 'resource' && data[i].type != 'scope') { - $scope.policies.push(data[i]); - } - } - + $scope.policies = data; $scope.searchLoaded = true; $scope.lastSearch = $scope.query.search; + if ($scope.detailsFilter) { + $scope.showDetails(); + } }); }; + + $scope.loadDetails = function (policy) { + if (policy.details) { + policy.details.loaded = !policy.details.loaded; + return; + } + + policy.details = {loaded: false}; + + ResourceServerPolicy.dependentPolicies({ + realm : $route.current.params.realm, + client : client.id, + id : policy.id + }, function(response) { + policy.dependentPolicies = response; + policy.details.loaded = true; + }); + } + + $scope.showDetails = function(item) { + if (item) { + $scope.loadDetails(item); + } else { + for (i = 0; i < $scope.policies.length; i++) { + $scope.loadDetails($scope.policies[i]); + } + } + }; }); module.controller('ResourceServerPermissionCtrl', function($scope, $http, $route, $location, realm, ResourceServer, ResourceServerPolicy, PolicyProvider, client) { @@ -508,6 +655,8 @@ module.controller('ResourceServerPermissionCtrl', function($scope, $http, $route first : 0 }; + $scope.listSizes = [5, 10, 20]; + PolicyProvider.query({ realm : $route.current.params.realm, client : client.id @@ -553,18 +702,42 @@ module.controller('ResourceServerPermissionCtrl', function($scope, $http, $route $scope.searchLoaded = false; ResourceServerPolicy.query($scope.query, function(data) { - $scope.policies = []; - - for (i = 0; i < data.length; i++) { - if (data[i].type == 'resource' || data[i].type == 'scope') { - $scope.policies.push(data[i]); - } - } - + $scope.policies = data; $scope.searchLoaded = true; $scope.lastSearch = $scope.query.search; + if ($scope.detailsFilter) { + $scope.showDetails(); + } }); }; + + $scope.loadDetails = function (policy) { + if (policy.details) { + policy.details.loaded = !policy.details.loaded; + return; + } + + policy.details = {loaded: false}; + + ResourceServerPolicy.associatedPolicies({ + realm : $route.current.params.realm, + client : client.id, + id : policy.id + }, function(response) { + policy.associatedPolicies = response; + policy.details.loaded = true; + }); + } + + $scope.showDetails = function(item) { + if (item) { + $scope.loadDetails(item); + } else { + for (i = 0; i < $scope.policies.length; i++) { + $scope.loadDetails($scope.policies[i]); + } + } + }; }); module.controller('ResourceServerPolicyDroolsDetailCtrl', function($scope, $http, $route, realm, client, PolicyController) { @@ -623,19 +796,64 @@ module.controller('ResourceServerPolicyResourceDetailCtrl', function($scope, $ro }, onInit : function() { - ResourceServerResource.query({realm : realm.realm, client : client.id}, function (data) { - $scope.resources = data; - }); - - ResourceServerPolicy.query({realm : realm.realm, client : client.id}, function (data) { - $scope.policies = []; - - for (i = 0; i < data.length; i++) { - if (data[i].type != 'resource' && data[i].type != 'scope') { - $scope.policies.push(data[i]); + $scope.resourcesUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + id: function(resource){ return resource._id; }, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; } + $scope.query = { + realm: realm.realm, + client : client.id, + name: query.term.trim(), + deep: false, + max : 20, + first : 0 + }; + ResourceServerResource.query($scope.query, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + object.text = object.name; + return object.name; } - }); + }; + + $scope.policiesUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; + } + $scope.query = { + realm: realm.realm, + client : client.id, + permission: false, + name: query.term.trim(), + max : 20, + first : 0 + }; + ResourceServerPolicy.query($scope.query, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + object.text = object.name; + return object.name; + } + }; $scope.applyToResourceType = function() { if ($scope.policy.config.default) { @@ -648,30 +866,69 @@ module.controller('ResourceServerPolicyResourceDetailCtrl', function($scope, $ro onInitUpdate : function(policy) { policy.config.default = eval(policy.config.default); - policy.config.resources = eval(policy.config.resources); - policy.config.applyPolicies = eval(policy.config.applyPolicies); + policy.config.resources = {}; + ResourceServerPolicy.resources({ + realm : $route.current.params.realm, + client : client.id, + id : policy.id + }, function(resources) { + resources[0].text = resources[0].name; + $scope.policy.config.resources = resources[0]; + }); + + policy.config.applyPolicies = []; + ResourceServerPolicy.associatedPolicies({ + realm : $route.current.params.realm, + client : client.id, + id : policy.id + }, function(policies) { + for (i = 0; i < policies.length; i++) { + policies[i].text = policies[i].name; + $scope.policy.config.applyPolicies.push(policies[i]); + } + }); }, onUpdate : function() { - $scope.policy.config.resources = JSON.stringify($scope.policy.config.resources); - $scope.policy.config.applyPolicies = JSON.stringify($scope.policy.config.applyPolicies); + $scope.policy.config.resources = JSON.stringify([$scope.policy.config.resources._id]); + var policies = []; + + for (i = 0; i < $scope.policy.config.applyPolicies.length; i++) { + policies.push($scope.policy.config.applyPolicies[i].id); + } + + $scope.policy.config.applyPolicies = JSON.stringify(policies); }, onInitCreate : function(newPolicy) { newPolicy.decisionStrategy = 'UNANIMOUS'; newPolicy.config = {}; - newPolicy.config.resources = ''; + newPolicy.config.resources = null; var resourceId = $location.search()['rsrid']; if (resourceId) { - newPolicy.config.resources = [resourceId]; + ResourceServerResource.get({ + realm : $route.current.params.realm, + client : client.id, + rsrid : resourceId + }, function(data) { + data.text = data.name; + $scope.policy.config.resources = data; + }); } }, onCreate : function() { - $scope.policy.config.resources = JSON.stringify($scope.policy.config.resources); - $scope.policy.config.applyPolicies = JSON.stringify($scope.policy.config.applyPolicies); + $scope.policy.config.resources = JSON.stringify([$scope.policy.config.resources._id]); + + var policies = []; + + for (i = 0; i < $scope.policy.config.applyPolicies.length; i++) { + policies.push($scope.policy.config.applyPolicies[i].id); + } + + $scope.policy.config.applyPolicies = JSON.stringify(policies); } }, realm, client, $scope); }); @@ -687,105 +944,241 @@ module.controller('ResourceServerPolicyScopeDetailCtrl', function($scope, $route }, onInit : function() { - ResourceServerScope.query({realm : realm.realm, client : client.id}, function (data) { - $scope.scopes = data; - }); - - ResourceServerResource.query({realm : realm.realm, client : client.id}, function (data) { - $scope.resources = data; - }); - - ResourceServerPolicy.query({realm : realm.realm, client : client.id}, function (data) { - $scope.policies = []; - - for (i = 0; i < data.length; i++) { - if (data[i].type != 'resource' && data[i].type != 'scope') { - $scope.policies.push(data[i]); + $scope.scopesUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; } - } - }); - - $scope.resolveScopes = function(policy, keepScopes) { - if (!keepScopes) { - policy.config.scopes = []; - } - - if (!policy) { - policy = $scope.policy; - } - - if (policy.config.resources != null) { - ResourceServerResource.get({ - realm : $route.current.params.realm, + $scope.query = { + realm: realm.realm, client : client.id, - rsrid : policy.config.resources - }, function(data) { - $scope.scopes = data.scopes; + name: query.term.trim(), + deep: false, + max : 20, + first : 0 + }; + ResourceServerScope.query($scope.query, function(response) { + data.results = response; + query.callback(data); }); - } else { - ResourceServerScope.query({realm : realm.realm, client : client.id}, function (data) { - $scope.scopes = data; + }, + formatResult: function(object, container, query) { + object.text = object.name; + return object.name; + } + }; + + $scope.resourcesUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + id: function(resource){ return resource._id; }, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; + } + $scope.query = { + realm: realm.realm, + client : client.id, + name: query.term.trim(), + deep: false, + max : 20, + first : 0 + }; + ResourceServerResource.query($scope.query, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + object.text = object.name; + return object.name; + } + }; + + $scope.policiesUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; + } + $scope.query = { + realm: realm.realm, + client : client.id, + permission: false, + name: query.term.trim(), + max : 20, + first : 0 + }; + ResourceServerPolicy.query($scope.query, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + object.text = object.name; + return object.name; + } + }; + + $scope.selectResource = function() { + if ($scope.policy.config.resources) { + ResourceServerResource.scopes({ + realm: $route.current.params.realm, + client: client.id, + rsrid: $scope.policy.config.resources._id + }, function (data) { + $scope.policy.config.resources.scopes = data; }); } } }, onInitUpdate : function(policy) { - if (policy.config.resources) { - policy.config.resources = eval(policy.config.resources); + policy.config.resources = eval(policy.config.resources); - if (policy.config.resources.length > 0) { - policy.config.resources = policy.config.resources[0]; - } else { - policy.config.resources = null; - } + if (policy.config.resources == null) { + policy.config.resources = []; } - $scope.resolveScopes(policy, true); + if (policy.config.resources.length > 0) { + ResourceServerResource.query({ + realm: $route.current.params.realm, + client: client.id, + _id: policy.config.resources[0], + deep: false + }, function (data) { + data[0].text = data[0].name; + $scope.policy.config.resources = data[0]; + ResourceServerResource.scopes({ + realm: $route.current.params.realm, + client: client.id, + rsrid: policy.config.resources[0] + }, function (data) { + $scope.policy.config.resources.scopes = data; + }); + ResourceServerPolicy.scopes({ + realm : $route.current.params.realm, + client : client.id, + id : policy.id + }, function(scopes) { + $scope.policy.config.scopes = []; + for (i = 0; i < scopes.length; i++) { + $scope.policy.config.scopes.push(scopes[i].id); + } + }); + }); + } else { + policy.config.resources = null; + ResourceServerPolicy.scopes({ + realm : $route.current.params.realm, + client : client.id, + id : policy.id + }, function(scopes) { + $scope.policy.config.scopes = []; + for (i = 0; i < scopes.length; i++) { + scopes[i].text = scopes[i].name; + $scope.policy.config.scopes.push(scopes[i]); + } + }); + } - policy.config.applyPolicies = eval(policy.config.applyPolicies); - policy.config.scopes = eval(policy.config.scopes); + policy.config.applyPolicies = []; + ResourceServerPolicy.associatedPolicies({ + realm : $route.current.params.realm, + client : client.id, + id : policy.id + }, function(policies) { + for (i = 0; i < policies.length; i++) { + policies[i].text = policies[i].name; + $scope.policy.config.applyPolicies.push(policies[i]); + } + }); }, onUpdate : function() { if ($scope.policy.config.resources != null) { - var resources = undefined; - - if ($scope.policy.config.resources.length != 0) { - resources = JSON.stringify([$scope.policy.config.resources]) - } - - $scope.policy.config.resources = resources; + $scope.policy.config.resources = JSON.stringify([$scope.policy.config.resources._id]); } - $scope.policy.config.scopes = JSON.stringify($scope.policy.config.scopes); - $scope.policy.config.applyPolicies = JSON.stringify($scope.policy.config.applyPolicies); + var scopes = []; + + for (i = 0; i < $scope.policy.config.scopes.length; i++) { + if ($scope.policy.config.resources == null) { + scopes.push($scope.policy.config.scopes[i].id); + } else { + scopes.push($scope.policy.config.scopes[i]); + } + } + + $scope.policy.config.scopes = JSON.stringify(scopes); + + var policies = []; + + for (i = 0; i < $scope.policy.config.applyPolicies.length; i++) { + policies.push($scope.policy.config.applyPolicies[i].id); + } + + $scope.policy.config.applyPolicies = JSON.stringify(policies); }, onInitCreate : function(newPolicy) { newPolicy.decisionStrategy = 'UNANIMOUS'; newPolicy.config = {}; - newPolicy.config.resources = ''; + newPolicy.config.resources = null; var scopeId = $location.search()['scpid']; if (scopeId) { - newPolicy.config.scopes = [scopeId]; + ResourceServerScope.get({ + realm: $route.current.params.realm, + client: client.id, + id: scopeId, + }, function (data) { + data.text = data.name; + if (!$scope.policy.config.scopes) { + $scope.policy.config.scopes = []; + } + $scope.policy.config.scopes.push(data); + }); } }, onCreate : function() { if ($scope.policy.config.resources != null) { - var resources = undefined; - - if ($scope.policy.config.resources.length != 0) { - resources = JSON.stringify([$scope.policy.config.resources]) - } - - $scope.policy.config.resources = resources; + $scope.policy.config.resources = JSON.stringify([$scope.policy.config.resources._id]); } - $scope.policy.config.scopes = JSON.stringify($scope.policy.config.scopes); - $scope.policy.config.applyPolicies = JSON.stringify($scope.policy.config.applyPolicies); + + var scopes = []; + + for (i = 0; i < $scope.policy.config.scopes.length; i++) { + if ($scope.policy.config.scopes[i].id) { + scopes.push($scope.policy.config.scopes[i].id); + } else { + scopes.push($scope.policy.config.scopes[i]); + } + } + + $scope.policy.config.scopes = JSON.stringify(scopes); + + var policies = []; + + for (i = 0; i < $scope.policy.config.applyPolicies.length; i++) { + policies.push($scope.policy.config.applyPolicies[i].id); + } + + $scope.policy.config.applyPolicies = JSON.stringify(policies); } }, realm, client, $scope); }); @@ -1250,23 +1643,58 @@ module.controller('ResourceServerPolicyAggregateDetailCtrl', function($scope, $r }, onInit : function() { - ResourceServerPolicy.query({realm : realm.realm, client : client.id}, function (data) { - $scope.policies = []; - - for (i = 0; i < data.length; i++) { - if (data[i].type != 'resource' && data[i].type != 'scope') { - $scope.policies.push(data[i]); + $scope.policiesUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; } + $scope.query = { + realm: realm.realm, + client : client.id, + permission: false, + name: query.term.trim(), + max : 20, + first : 0 + }; + ResourceServerPolicy.query($scope.query, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + object.text = object.name; + return object.name; + } + }; + }, + + onInitUpdate : function(policy) { + policy.config.applyPolicies = []; + ResourceServerPolicy.associatedPolicies({ + realm : $route.current.params.realm, + client : client.id, + id : policy.id + }, function(policies) { + for (i = 0; i < policies.length; i++) { + policies[i].text = policies[i].name; + $scope.policy.config.applyPolicies.push(policies[i]); } }); }, - onInitUpdate : function(policy) { - policy.config.applyPolicies = eval(policy.config.applyPolicies); - }, - onUpdate : function() { - $scope.policy.config.applyPolicies = JSON.stringify($scope.policy.config.applyPolicies); + var policies = []; + + for (i = 0; i < $scope.policy.config.applyPolicies.length; i++) { + policies.push($scope.policy.config.applyPolicies[i].id); + } + + $scope.policy.config.applyPolicies = JSON.stringify(policies); }, onInitCreate : function(newPolicy) { @@ -1275,7 +1703,13 @@ module.controller('ResourceServerPolicyAggregateDetailCtrl', function($scope, $r }, onCreate : function() { - $scope.policy.config.applyPolicies = JSON.stringify($scope.policy.config.applyPolicies); + var policies = []; + + for (i = 0; i < $scope.policy.config.applyPolicies.length; i++) { + policies.push($scope.policy.config.applyPolicies[i].id); + } + + $scope.policy.config.applyPolicies = JSON.stringify(policies); } }, realm, client, $scope); }); @@ -1357,9 +1791,9 @@ module.service("PolicyController", function($http, $route, $location, ResourceSe } } else { ResourceServerPolicy.get({ - realm : $route.current.params.realm, + realm: realm.realm, client : client.id, - id : $route.current.params.id, + id: $route.current.params.id }, function(data) { $scope.originalPolicy = data; var policy = angular.copy(data); @@ -1408,25 +1842,31 @@ module.service("PolicyController", function($http, $route, $location, ResourceSe $scope.remove = function() { var msg = ""; - if ($scope.policy.dependentPolicies.length > 0) { - msg = "

This policy is being used by other policies:

"; - msg += "
    "; - for (i = 0; i < $scope.policy.dependentPolicies.length; i++) { - msg+= "
  • " + $scope.policy.dependentPolicies[i].name + "
  • "; - } - msg += "
"; - msg += "

If you remove this policy, the policies above will be affected and will not be associated with this policy anymore.

"; - } - - AuthzDialog.confirmDeleteWithMsg($scope.policy.name, "Policy", msg, function() { - ResourceServerPolicy.delete({realm : $scope.realm.realm, client : $scope.client.id, id : $scope.policy.id}, null, function() { - if (delegate.isPermission()) { - $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/permission"); - Notifications.success("The permission has been deleted."); - } else { - $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/policy"); - Notifications.success("The policy has been deleted."); + ResourceServerPolicy.dependentPolicies({ + realm : $route.current.params.realm, + client : client.id, + id : $scope.policy.id + }, function (dependentPolicies) { + if (dependentPolicies.length > 0 && !$scope.deleteConsent) { + msg = "

This policy is being used by other policies:

"; + msg += "
    "; + for (i = 0; i < dependentPolicies.length; i++) { + msg+= "
  • " + dependentPolicies[i].name + "
  • "; } + msg += "
"; + msg += "

If you remove this policy, the policies above will be affected and will not be associated with this policy anymore.

"; + } + + AuthzDialog.confirmDeleteWithMsg($scope.policy.name, "Policy", msg, function() { + ResourceServerPolicy.delete({realm : $scope.realm.realm, client : $scope.client.id, id : $scope.policy.id}, null, function() { + if (delegate.isPermission()) { + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/permission"); + Notifications.success("The permission has been deleted."); + } else { + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/policy"); + Notifications.success("The policy has been deleted."); + } + }); }); }); } @@ -1465,13 +1905,8 @@ module.controller('PolicyEvaluateCtrl', function($scope, $http, $route, $locatio $scope.authzRequest.context = {}; $scope.authzRequest.context.attributes = {}; $scope.authzRequest.roleIds = []; - $scope.newResource = {}; $scope.resultUrl = resourceUrl + '/partials/authz/policy/resource-server-policy-evaluate-result.html'; - ResourceServerScope.query({realm : realm.realm, client : client.id}, function (data) { - $scope.scopes = data; - }); - $scope.addContextAttribute = function() { if (!$scope.newContextAttribute.value || $scope.newContextAttribute.value == '') { Notifications.error("You must provide a value to a context attribute."); @@ -1573,33 +2008,39 @@ module.controller('PolicyEvaluateCtrl', function($scope, $http, $route, $locatio } $scope.setApplyToResourceType = function() { - if ($scope.applyResourceType) { - ResourceServerScope.query({realm : realm.realm, client : client.id}, function (data) { - $scope.scopes = data; - }); - } - delete $scope.newResource; $scope.authzRequest.resources = []; } $scope.addResource = function() { - var resource = {}; + var resource = angular.copy($scope.newResource); - resource.id = $scope.newResource._id; + if (!resource) { + resource = {}; + } - for (i = 0; i < $scope.resources.length; i++) { - if ($scope.resources[i]._id == resource.id) { - resource.name = $scope.resources[i].name; - break; + delete resource.text; + + if (!$scope.newScopes || (resource._id != null && $scope.newScopes.length > 0 && $scope.newScopes[0].id)) { + $scope.newScopes = []; + } + + var scopes = []; + + for (i = 0; i < $scope.newScopes.length; i++) { + if ($scope.newScopes[i].name) { + scopes.push($scope.newScopes[i].name); + } else { + scopes.push($scope.newScopes[i]); } } - resource.scopes = $scope.newResource.scopes; + resource.scopes = scopes; $scope.authzRequest.resources.push(resource); delete $scope.newResource; + delete $scope.newScopes; } $scope.removeResource = function(index) { @@ -1610,20 +2051,11 @@ module.controller('PolicyEvaluateCtrl', function($scope, $http, $route, $locatio if ($scope.newResource._id) { $scope.newResource.scopes = []; $scope.scopes = []; - ResourceServerResource.get({ + ResourceServerResource.scopes({ realm: $route.current.params.realm, - client : client.id, + client: client.id, rsrid: $scope.newResource._id }, function (data) { - $scope.scopes = data.scopes; - if (data.typedScopes) { - for (i=0;i 0 && $scope.newScopes[0].id)) { + $scope.newScopes = []; + } + + var scopes = angular.copy($scope.newScopes); + + for (i = 0; i < scopes.length; i++) { + delete scopes[i].text; + } + + $scope.authzRequest.resources[0].scopes = scopes; } $http.post(authUrl + '/admin/realms/'+ $route.current.params.realm + '/clients/' + client.id + '/authz/resource-server/policy/evaluate' @@ -1697,9 +2139,64 @@ module.controller('PolicyEvaluateCtrl', function($scope, $http, $route, $locatio } }; - ResourceServerResource.query({realm : realm.realm, client : client.id}, function (data) { - $scope.resources = data; - }); + $scope.resourcesUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + id: function(resource){ return resource._id; }, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; + } + $scope.query = { + realm: realm.realm, + client : client.id, + name: query.term.trim(), + deep: false, + max : 20, + first : 0 + }; + ResourceServerResource.query($scope.query, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + object.text = object.name; + return object.name; + } + }; + + $scope.scopesUiSelect = { + minimumInputLength: 1, + delay: 500, + allowClear: true, + query: function (query) { + var data = {results: []}; + if ('' == query.term.trim()) { + query.callback(data); + return; + } + $scope.query = { + realm: realm.realm, + client : client.id, + name: query.term.trim(), + deep: false, + max : 20, + first : 0 + }; + ResourceServerScope.query($scope.query, function(response) { + data.results = response; + query.callback(data); + }); + }, + formatResult: function(object, container, query) { + object.text = object.name; + return object.name; + } + }; ResourceServer.get({ realm : $route.current.params.realm, @@ -1717,5 +2214,4 @@ module.controller('PolicyEvaluateCtrl', function($scope, $http, $route, $locatio $scope.authzRequest.userId = user.id; } - }); \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-services.js b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-services.js index 795cf1d735..1c4f584bd8 100644 --- a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-services.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-services.js @@ -16,7 +16,9 @@ module.factory('ResourceServerResource', function($resource) { rsrid : '@rsrid' }, { 'update' : {method : 'PUT'}, - 'search' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/resource/search', method : 'GET'} + 'search' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/resource/search', method : 'GET'}, + 'scopes' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/resource/:rsrid/scopes', method : 'GET', isArray: true}, + 'permissions' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/resource/:rsrid/permissions', method : 'GET', isArray: true} }); }); @@ -27,7 +29,9 @@ module.factory('ResourceServerScope', function($resource) { id : '@id' }, { 'update' : {method : 'PUT'}, - 'search' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/scope/search', method : 'GET'} + 'search' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/scope/search', method : 'GET'}, + 'resources' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/scope/:id/resources', method : 'GET', isArray: true}, + 'permissions' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/scope/:id/permissions', method : 'GET', isArray: true}, }); }); @@ -38,7 +42,11 @@ module.factory('ResourceServerPolicy', function($resource) { id : '@id' }, { 'update' : {method : 'PUT'}, - 'search' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/policy/search', method : 'GET'} + 'search' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/policy/search', method : 'GET'}, + 'associatedPolicies' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/policy/:id/associatedPolicies', method : 'GET', isArray: true}, + 'dependentPolicies' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/policy/:id/dependentPolicies', method : 'GET', isArray: true}, + 'scopes' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/policy/:id/scopes', method : 'GET', isArray: true}, + 'resources' : {url: authUrl + '/admin/realms/:realm/clients/:client/authz/resource-server/policy/:id/resources', method : 'GET', isArray: true} }); }); diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js index 84e25207aa..39744628af 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js @@ -837,6 +837,11 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates, "transient", "persistent" ]; + $scope.xmlKeyNameTranformers = [ + "NONE", + "KEY_ID", + "CERT_SUBJECT" + ]; $scope.canonicalization = [ {name: "EXCLUSIVE", value: "http://www.w3.org/2001/10/xml-exc-c14n#" }, @@ -866,6 +871,7 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates, $scope.samlEncrypt = false; $scope.samlForcePostBinding = false; $scope.samlForceNameIdFormat = false; + $scope.samlXmlKeyNameTranformer = $scope.xmlKeyNameTranformers[1]; $scope.disableAuthorizationTab = !client.authorizationServicesEnabled; $scope.disableServiceAccountRolesTab = !client.serviceAccountsEnabled; $scope.disableCredentialsTab = client.publicClient; @@ -918,6 +924,13 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates, $scope.samlServerSignatureEnableKeyInfoExtension = false; } } + if ($scope.client.attributes['saml.server.signature.keyinfo.xmlSigKeyInfoKeyNameTransformer'] === 'NONE') { + $scope.samlXmlKeyNameTranformer = $scope.xmlKeyNameTranformers[0]; + } else if ($scope.client.attributes['saml.server.signature.keyinfo.xmlSigKeyInfoKeyNameTransformer'] === 'KEY_ID') { + $scope.samlXmlKeyNameTranformer = $scope.xmlKeyNameTranformers[1]; + } else if ($scope.client.attributes['saml.server.signature.keyinfo.xmlSigKeyInfoKeyNameTransformer'] === 'CERT_SUBJECT') { + $scope.samlXmlKeyNameTranformer = $scope.xmlKeyNameTranformers[2]; + } if ($scope.client.attributes["saml.assertion.signature"]) { if ($scope.client.attributes["saml.assertion.signature"] == "true") { $scope.samlAssertionSignature = true; @@ -1037,6 +1050,10 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates, $scope.client.attributes['saml_name_id_format'] = $scope.nameIdFormat; }; + $scope.changeSamlSigKeyNameTranformer = function() { + $scope.client.attributes['saml.server.signature.keyinfo.xmlSigKeyInfoKeyNameTransformer'] = $scope.samlXmlKeyNameTranformer; + }; + $scope.changeUserInfoSignedResponseAlg = function() { if ($scope.userInfoSignedResponseAlg === 'unsigned') { $scope.client.attributes['user.info.response.signature.alg'] = null; @@ -1079,6 +1096,8 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates, } $scope.client.publicClient = false; $scope.client.serviceAccountsEnabled = true; + } else if ($scope.client.bearerOnly) { + $scope.client.serviceAccountsEnabled = false; } } diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js index e451339015..12d37aaae8 100644 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js @@ -764,11 +764,17 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload "RSA_SHA512", "DSA_SHA1" ]; + $scope.xmlKeyNameTranformers = [ + "NONE", + "KEY_ID", + "CERT_SUBJECT" + ]; if (instance && instance.alias) { } else { $scope.identityProvider.config.nameIDPolicyFormat = $scope.nameIdFormats[0].format; $scope.identityProvider.config.signatureAlgorithm = $scope.signatureAlgorithms[1]; + $scope.identityProvider.config.samlXmlKeyNameTranformer = $scope.xmlKeyNameTranformers[1]; } } @@ -1120,6 +1126,14 @@ module.controller('RealmKeysProvidersCtrl', function($scope, Realm, realm, $http type: 'org.keycloak.keys.KeyProvider' }, function(data) { $scope.instances = data; + + for (var i = 0; i < $scope.instances.length; i++) { + for (var j = 0; j < $scope.providers.length; j++) { + if ($scope.providers[j].id === $scope.instances[i].providerId) { + $scope.instances[i].provider = $scope.providers[j]; + } + } + } }); $scope.addProvider = function(provider) { diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-resource-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-resource-detail.html index a8d45122e0..10e31707d0 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-resource-detail.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-resource-detail.html @@ -39,9 +39,7 @@
- +
{{:: 'authz-permission-resource-resource.tooltip' | translate}}
@@ -58,9 +56,7 @@
- +
{{:: 'authz-policy-apply-policy.tooltip' | translate}} diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-scope-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-scope-detail.html index 3d1660b27e..90a3dc698b 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-scope-detail.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/provider/resource-server-policy-scope-detail.html @@ -32,12 +32,7 @@
- +
{{:: 'authz-permission-scope-resource.tooltip' | translate}}