nasty merge

This commit is contained in:
Bill Burke 2013-11-05 23:12:07 -05:00
commit 2861ea0e96
97 changed files with 2133 additions and 1870 deletions

View file

@ -43,7 +43,34 @@ body {
background-image: url(img/feedback-info-sign.png);
background-color: #e4f3fa;
}
#idletimeout {
background: #CC5100;
color: #FFFFFF;
font-size: 1.1em;
padding: 0.90909090909091em;
text-align: center;
display: none;
}
#idletimeout a {
color: #fff;
font-weight: bold;
}
.loading span {
background: url(img/loader.gif) no-repeat center top;
position: fixed;
z-index: 1000;
top: 50%;
left: 50%;
margin-top: -2.27272727272727em;
margin-left: -2.27272727272727em;
padding-top: 2.90909090909091em;
font-size: 1.1em;
color: #666;
}
/* Header */
.header.rcue {
z-index: 50;
}
.header.rcue .navbar.utility {
background-color: #393F45;
border-bottom: 1px solid #53565B;
@ -65,36 +92,6 @@ body {
min-height: 42px;
max-width: 1170px;
}
.header.rcue .navbar.primary .select-rcue {
font-size: 0.76923076923077em;
margin-left: 1em;
margin-top: 0.7em;
display: inline-block;
vertical-align: middle;
background-color: #555a5e;
background-image: none;
background-image: url(img/sprite-arrow-down.png);
background-repeat: no-repeat;
background-position: right -26px;
border: 1px solid #676c6e;
border-radius: 2px;
padding-left: 0;
}
.header.rcue .navbar.primary .select-rcue:hover {
border-color: #7e8385;
}
.header.rcue .navbar.primary .select-rcue select {
color: #fff;
}
.header.rcue .navbar.primary .select-rcue select:-moz-focusring {
color: transparent;
text-shadow: 0 0 0 #fff;
}
.header.rcue .navbar.primary .select-rcue select option {
background-color: #fff;
color: #333;
padding: 0.36363636363636em 0.90909090909091em;
}
.header.rcue .navbar.primary .nav > li {
/*
.dropdown {
@ -215,7 +212,6 @@ body {
.header.rcue .navbar.primary .nav > li .select-rcue select option {
background-color: #fff;
color: #333;
padding: 0.36363636363636em 0.90909090909091em;
}
.header.rcue .navbar.primary .nav > li a#refresh {
border: none;
@ -469,6 +465,42 @@ table + .feedback.inline.warning {
td.token-cell button {
margin-top: -1px;
}
/* Modal boxes */
.modal .modal-dialog {
padding-top: 10em;
}
.modal .modal-content {
box-shadow: none;
border-radius: 0;
border: 1px solid #666;
}
.modal .modal-header {
background-color: #f8f8f8;
padding: 1.5em 2em;
border-bottom: none;
}
.modal .modal-header h3 {
font-size: 1.3em;
font-weight: bold;
font-family: "Open Sans", sans-serif;
margin: 0;
}
.modal .modal-body p {
font-size: 1.1em;
}
.modal .modal-body p.primary {
font-size: 1.1em;
font-weight: bold;
margin-bottom: 0.45454545454545em;
}
.modal .modal-footer {
border-top: 0;
}
.modal .modal-footer .primary,
.modal .modal-footer .destructive {
float: right;
margin-left: 0.90909090909091em;
}
/* Page: User Account */
.user form fieldset div:first-child {
margin-top: 1em;

View file

@ -60,11 +60,43 @@ body {
}
}
#idletimeout {
background: #CC5100;
color: #FFFFFF;
font-size: 1.1em;
padding: 0.90909090909091em;
text-align: center;
display: none;
a {
color: #fff;
font-weight: bold;
}
}
.loading {
span {
background: url(img/loader.gif) no-repeat center top;
position: fixed;
z-index: 1000;
top: 50%;
left: 50%;
margin-top: -2.27272727272727em;
margin-left: -2.27272727272727em;
padding-top: 2.90909090909091em;
font-size: 1.1em;
color: #666;
}
}
/* Header */
.header.rcue {
z-index: 50;
.navbar.utility {
background-color: #393F45;
border-bottom: 1px solid #53565B;
@ -89,41 +121,6 @@ body {
max-width: 1170px;
}
.select-rcue {
font-size: 0.76923076923077em;
margin-left: 1em;
margin-top: 0.7em;
display: inline-block;
vertical-align: middle;
background-color: #555a5e;
background-image: none;
background-image: url(img/sprite-arrow-down.png);
background-repeat: no-repeat;
background-position: right -26px;
border: 1px solid #676c6e;
border-radius: 2px;
padding-left: 0;
&:hover {
border-color: #7e8385;
}
select {
color: #fff;
&:-moz-focusring {
color: transparent;
text-shadow: 0 0 0 #fff;
}
option {
background-color: #fff;
color: #333;
padding: 0.36363636363636em 0.90909090909091em;
}
}
}
.nav > li {
.dropdown-label {
@ -165,7 +162,6 @@ body {
option {
background-color: #fff;
color: #333;
padding: 0.36363636363636em 0.90909090909091em;
}
}
}
@ -567,6 +563,59 @@ td.token-cell button {
}
/* Modal boxes */
.modal {
.modal-dialog {
padding-top: 10em;
}
.modal-content {
box-shadow: none;
border-radius: 0;
border: 1px solid #666;
}
.modal-header {
background-color: #f8f8f8;
padding: 1.5em 2em;
border-bottom: none;
h3 {
font-size: 1.3em;
font-weight: bold;
font-family: @open-sans;
margin: 0;
}
}
.modal-body {
p {
font-size: 1.1em;
&.primary {
font-size: 1.1em;
font-weight: bold;
margin-bottom: 0.45454545454545em;
}
}
}
.modal-footer {
border-top: 0;
.primary,
.destructive {
float: right;
margin-left: 0.90909090909091em;
}
}
}
/* Page: User Account */
.user {

View file

@ -16,12 +16,14 @@ input[type="email"],
textarea {
font-size: 1.1em;
padding: 0 0.545454545454545em;
min-width: 18.1818181818182em;
height: 2.36363636363636em;
/* 26px */
border: 1px #b6b6b6 solid;
border-radius: 2px;
box-shadow: inset 0px 2px 2px rgba(0, 0, 0, 0.1);
color: #333;
width: 18.1818em;
}
input[type="text"]:hover,
input[type="password"]:hover,
@ -51,6 +53,36 @@ input[type="email"].error:focus,
textarea.error:focus {
box-shadow: 0 0 5px #ba1212;
}
input[type="text"].tiny,
input[type="password"].tiny,
input[type="email"].tiny,
textarea.tiny {
width: 4.54545454545455em;
}
input[type="text"].small,
input[type="password"].small,
input[type="email"].small,
textarea.small {
width: 9.09090909090909em;
}
input[type="text"].medium,
input[type="password"].medium,
input[type="email"].medium,
textarea.medium {
width: 18.1818em;
}
input[type="text"].large,
input[type="password"].large,
input[type="email"].large,
textarea.large {
width: 27.2727272727273em;
}
input[type="text"].xlarge,
input[type="password"].xlarge,
input[type="email"].xlarge,
textarea.xlarge {
width: 36.3636363636364em;
}
textarea {
padding: 0.45em 0.545454545454545em;
height: auto;
@ -252,7 +284,7 @@ button.primary:focus,
.search-comp .icon-search {
position: absolute;
right: 0.2em;
top: 0.4em;
top: 0.6em;
opacity: 0.5;
filter: alpha(opacity=50);
}
@ -457,6 +489,8 @@ fieldset.border-top {
.form-group .required {
font-size: 1.1em;
color: #CB2915;
display: inline-block;
margin-top: -0.09090909090909em;
}
legend + .form-group {
padding-top: 1em;
@ -550,7 +584,7 @@ input[type="email"].tiny {
}
.select-rcue,
.select2-container .select2-choice {
height: 26px;
height: 2.6em;
border: 1px #b6b6b6 solid;
border-radius: 2px;
box-shadow: inset 0px 2px 2px rgba(0, 0, 0, 0.1);
@ -593,7 +627,7 @@ input[type="email"].tiny {
}
.select-rcue option {
line-height: 2em;
padding-left: 0.90909090909091em;
padding: 0.363636em 0.909091em;
}
.select-rcue option:hover {
background-color: #d5ecf9;
@ -788,7 +822,8 @@ input[type="email"].tiny {
margin-top: 3em;
margin-bottom: 5em;
}
.form-actions .primary {
.form-actions .primary,
.form-actions .destructive {
float: right;
margin-left: 0.90909090909091em;
}

View file

@ -23,12 +23,12 @@ input[type="email"],
textarea {
font-size: 1.1em;
padding: 0 0.545454545454545em;
min-width: 18.1818181818182em;
height: 2.36363636363636em;
height: 2.36363636363636em; /* 26px */
border: 1px #b6b6b6 solid;
border-radius: 2px;
box-shadow: inset 0px 2px 2px rgba(0,0,0,0.1);
color: #333;
width: 18.1818em;
&:hover {
border-color: #62afdb;
@ -49,6 +49,26 @@ textarea {
box-shadow: 0 0 5px #ba1212;
}
}
&.tiny {
width: 4.54545454545455em;
}
&.small {
width: 9.09090909090909em;
}
&.medium {
width: 18.1818em;
}
&.large {
width: 27.2727272727273em;
}
&.xlarge {
width: 36.3636363636364em
}
}
textarea {
@ -280,7 +300,7 @@ button.primary,
.icon-search {
position: absolute;
right: 0.2em;
top: 0.4em;
top: 0.6em;
opacity: 0.5;
filter: alpha(opacity=50);
@ -532,6 +552,8 @@ fieldset.border-top {
.required {
font-size: 1.1em;
color: #CB2915;
display: inline-block;
margin-top: -0.09090909090909em;
}
}
@ -656,7 +678,7 @@ input[type="email"] {
.select-rcue,
.select2-container .select2-choice {
height: 26px;
height: 2.6em;
border: 1px #b6b6b6 solid;
border-radius: 2px;
box-shadow: inset 0px 2px 2px rgba(0,0,0,0.1);
@ -707,7 +729,7 @@ input[type="email"] {
option {
line-height: 2em;
padding-left: 0.90909090909091em;
padding: 0.363636em 0.909091em;
&:hover {
background-color: #d5ecf9;
@ -940,7 +962,8 @@ input[type="email"] {
margin-top: 3em;
margin-bottom: 5em;
.primary {
.primary,
.destructive {
float: right;
margin-left: 0.90909090909091em;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="12px" height="400px" viewBox="0 0 12 400" enable-background="new 0 0 12 400" xml:space="preserve">
<rect x="6.001" opacity="0.07" fill="#FFFFFF" enable-background="new " width="0.997" height="190"/>
<rect x="6" y="209" opacity="0.07" fill="#FFFFFF" enable-background="new " width="1" height="191"/>
<g opacity="0.15">
<path fill="#FFFFFF" d="M6.501,200.066c0,1.047-0.264,1.864-0.791,2.452S4.454,203.4,3.524,203.4c-0.574,0-1.084-0.135-1.529-0.404
c-0.445-0.27-0.789-0.656-1.031-1.16c-0.242-0.504-0.363-1.094-0.363-1.77c0-1.047,0.262-1.862,0.785-2.446
c0.523-0.584,1.25-0.876,2.18-0.876c0.898,0,1.612,0.299,2.142,0.896C6.238,198.237,6.501,199.047,6.501,200.066z M1.608,200.066
c0,0.821,0.164,1.446,0.492,1.875s0.811,0.645,1.447,0.645c0.636,0,1.12-0.214,1.45-0.643c0.33-0.428,0.495-1.053,0.495-1.877
c0-0.816-0.165-1.437-0.495-1.86s-0.817-0.636-1.462-0.636c-0.637,0-1.117,0.209-1.441,0.627
C1.77,198.615,1.608,199.238,1.608,200.066z"/>
<path fill="#FFFFFF" d="M11.136,196.744c0.285,0,0.541,0.023,0.768,0.07l-0.135,0.902c-0.266-0.059-0.5-0.088-0.703-0.088
c-0.52,0-0.964,0.211-1.333,0.633c-0.369,0.422-0.554,0.947-0.554,1.576v3.445H8.206v-6.422h0.803l0.111,1.189h0.047
c0.238-0.418,0.525-0.74,0.861-0.967C10.364,196.855,10.733,196.744,11.136,196.744z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -1,284 +0,0 @@
body {
font-size: 62.5%;
min-height: 60em;
min-width: 120em;
}
.rcue-login-register {
background-color: #1D2226;
background-image: url("img/login-screen-background.jpg");
background-position: top left;
background-size: auto;
background-repeat: no-repeat;
color: #fff;
/*
.form-area {
background-image: url(img/login-register-separator.svg);
background-repeat: no-repeat;
background-position: 40.2em center;
}
*/
/* Login area */
/* Social login area */
/* Info area */
}
.rcue-login-register h1 a {
position: absolute;
top: 5em;
right: 6.4em;
}
.rcue-login-register .content {
position: absolute;
bottom: 10%;
width: 100%;
min-width: 76em;
}
.rcue-login-register h2 {
padding-left: 4.34782608695652em;
font-family: "Overpass", sans-serif;
font-size: 2.3em;
font-weight: 100;
text-transform: uppercase;
letter-spacing: 0.005em;
}
.rcue-login-register h2 strong {
font-weight: bold;
}
.rcue-login-register .background-area {
border-top: 0.1em rgba(255, 255, 255, 0.05) solid;
border-bottom: 0.1em rgba(255, 255, 255, 0.05) solid;
background-color: rgba(0, 0, 0, 0.3);
padding: 3em 0 3em 10em;
margin-top: 2.7em;
width: 100%;
min-width: 120em;
}
.rcue-login-register .background-area section {
float: left;
padding: 1.5em 4.5em 1.5em 4.6em;
width: auto;
position: relative;
}
.rcue-login-register .background-area section h3 {
display: none;
}
.rcue-login-register .background-area section:first-child {
padding-right: 4.5em;
}
.rcue-login-register .form-area.social {
background-image: url(img/login-register-social-separators.svg);
background-position: 39.6em center;
}
.rcue-login-register section.app-form {
padding-left: 0;
position: relative;
}
.rcue-login-register form > div {
margin-bottom: 1em;
}
.rcue-login-register label,
.rcue-login-register .social-login > p {
display: inline-block;
font-size: 1.4em;
font-weight: 400;
}
.rcue-login-register label {
width: 6.07142857142857em;
/* 85px */
}
.rcue-login-register label.two-lines {
float: left;
margin-top: -0.28571428571429em;
/* -4px */
line-height: 1.1em;
}
.rcue-login-register input[type="text"],
.rcue-login-register input[type="password"] {
width: 24.7272727272727em;
/* 272px */
}
.rcue-login-register form > div.aside-btn {
float: left;
font-size: 1.1em;
margin-left: 7.72727272727273em;
/* 85px */
margin-top: 0.90909090909091em;
/* 10px */
margin-bottom: 0;
}
.rcue-login-register form > div.aside-btn label {
font-size: 1em;
width: auto;
}
.rcue-login-register form > div.aside-btn input[type="checkbox"] {
margin-bottom: 0.54545454545455em;
/* 6px */
}
.rcue-login-register form > input[type="button"] {
float: right;
margin-top: 0.76923076923077em;
/* 10px */
}
.rcue-login-register p.subtitle {
font-size: 1.1em;
color: #999;
position: absolute;
right: 4.09090909090909em;
top: -0.636363636363636em;
}
.rcue-login-register .feedback {
left: 32.7em;
top: -9.2em;
min-width: 35em;
}
.rcue-login-register.reset .feedback {
left: 35.7em;
}
.rcue-login-register section.social-login > span {
display: none;
}
.rcue-login-register section.social-login > p {
float: left;
margin-top: 0.28571428571429em;
/* 14px */
width: 6.78571428571429em;
/* 95px */
}
.rcue-login-register section.social-login > ul {
float: left;
}
.rcue-login-register section.social-login li {
margin-bottom: 2em;
}
.rcue-login-register section.social-login li:last-child {
margin-bottom: 0;
}
.rcue-login-register section.info-area {
padding-right: 0;
}
.rcue-login-register section.info-area p,
.rcue-login-register section.info-area li {
font-size: 1.4em;
margin-bottom: 1.64285714285714em;
/* 23px */
}
.rcue-login-register section.info-area li {
color: #999;
margin-bottom: 1em;
}
.rcue-login-register section.info-area li:last-child {
margin-bottom: 0;
}
@media screen and (min-width: 1280px) {
.rcue-login-register {
background-size: 100% auto;
}
}
/* Social buttons */
.zocial,
a.zocial {
padding: 0;
line-height: 2.3em;
height: 2.3em;
width: 131px;
border-radius: 2px;
box-shadow: none;
background-image: none;
text-shadow: none;
}
.zocial .text,
a.zocial .text {
font-size: 1.2em;
line-height: 1.25em;
text-align: center;
display: block;
font-family: "Open Sans", sans-serif;
font-weight: normal;
border-left: 1px solid rgba(0, 0, 0, 0.15);
margin-left: 3em;
/* 36 px */
margin-top: 0.25em;
/* 3px */
}
.zocial:hover,
a.zocial:hover,
.zocial:active,
a.zocial:active,
.zocial:focus,
a.zocial:focus {
text-decoration: none;
background-image: none;
}
.zocial:hover,
a.zocial:hover {
background-image: linear-gradient(rgba(0, 0, 0, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);
}
.zocial:before,
a.zocial:before {
margin: 0;
padding: 0;
box-shadow: none;
border: none;
width: 3em;
/* 36px */
}
.zocial.facebook:before {
width: 2.66666666666667em;
/* 32px */
}
/* Register page */
.rcue-login-register.register label {
width: 7.5em;
/* 105px */
}
.rcue-login-register.register input[type="text"],
.rcue-login-register.register input[type="email"],
.rcue-login-register.register input[type="password"] {
width: 22.9090909090909em;
/* 252px */
}
.rcue-login-register.register form > div.aside-btn {
margin-left: 9.54545454545454em;
/* 105px */
width: 12.5454545454546em;
/* 138px */
}
.rcue-login-register.register form > div.aside-btn p {
line-height: 1.3em;
}
/* Customer login */
.rcue-login-register.customer {
background-image: url("img/customer-login-screen-bg2.jpg");
}
.rcue-login-register.customer p.powered {
font-size: 1.1em;
margin-top: 1.27272727272727em;
text-align: right;
margin-right: 5.81818181818182em;
}
.rcue-login-register.customer p.powered a {
color: #666;
}
.rcue-login-register.customer p.powered a:hover {
color: #0099D3;
}

View file

@ -1,322 +0,0 @@
body {
font-size: 62.5%;
min-height: 60em;
min-width: 120em;
}
.rcue-login-register {
background-color: #1D2226;
background-image: url("img/login-screen-background.jpg");
background-position: top left;
background-size: auto;
background-repeat: no-repeat;
color: #fff;
h1 a {
position: absolute;
top: 5em;
right: 6.4em;
}
.content {
position: absolute;
bottom: 10%;
width: 100%;
min-width: 76em;
}
h2 {
padding-left: 4.34782608695652em;
font-family: "Overpass", sans-serif;
font-size: 2.3em;
font-weight: 100;
text-transform: uppercase;
letter-spacing: 0.005em;
strong {
font-weight: bold;
}
}
.background-area {
border-top: 0.1em rgba(255, 255, 255, 0.05) solid;
border-bottom: 0.1em rgba(255, 255, 255, 0.05) solid;
background-color: rgba(0, 0, 0, 0.3);
padding: 3em 0 3em 10em;
margin-top: 2.7em;
width: 100%;
min-width: 120em;
section {
float: left;
padding: 1.5em 4.5em 1.5em 4.6em;
width: auto;
position: relative;
h3 {
display: none;
}
&:first-child {
padding-right: 4.5em;
}
}
}
/*
.form-area {
background-image: url(img/login-register-separator.svg);
background-repeat: no-repeat;
background-position: 40.2em center;
}
*/
.form-area.social {
background-image: url(img/login-register-social-separators.svg);
background-position: 39.6em center;
}
/* Login area */
section.app-form {
padding-left: 0;
position: relative;
}
form > div {
margin-bottom: 1em;
}
label,
.social-login > p {
display: inline-block;
font-size: 1.4em;
font-weight: 400;
}
label {
width: 6.07142857142857em; /* 85px */
}
label.two-lines {
float: left;
margin-top: -0.28571428571429em; /* -4px */
line-height: 1.1em;
}
input[type="text"],
input[type="password"] {
width: 24.7272727272727em; /* 272px */
}
form > div.aside-btn {
float: left;
font-size: 1.1em;
margin-left: 7.72727272727273em; /* 85px */
margin-top: 0.90909090909091em; /* 10px */
margin-bottom: 0;
label {
font-size: 1em;
width: auto;
}
input[type="checkbox"] {
margin-bottom: 0.54545454545455em; /* 6px */
}
}
form > input[type="button"] {
float: right;
margin-top: 0.76923076923077em; /* 10px */
}
p.subtitle {
font-size: 1.1em;
color: #999;
position: absolute;
right: 4.09090909090909em;
top: -0.636363636363636em;
}
.feedback {
left: 32.7em;
top: -9.2em;
min-width: 35em;
}
&.reset .feedback {
left: 35.7em;
}
/* Social login area */
section.social-login {
> span {
display: none;
}
> p {
float: left;
margin-top: 0.28571428571429em; /* 14px */
width: 6.78571428571429em; /* 95px */
}
> ul {
float: left;
}
li {
margin-bottom: 2em;
&:last-child {
margin-bottom: 0;
}
}
}
/* Info area */
section.info-area {
padding-right: 0;
p,
li {
font-size: 1.4em;
margin-bottom: 1.64285714285714em; /* 23px */
}
li {
color: #999;
margin-bottom: 1em;
}
li:last-child {
margin-bottom: 0;
}
}
}
@media screen and (min-width: 1280px) {
.rcue-login-register {
background-size: 100% auto;
}
}
/* Social buttons */
.zocial,
a.zocial {
padding: 0;
line-height: 2.3em;
height: 2.3em;
width: 131px;
border-radius: 2px;
box-shadow: none;
background-image: none;
text-shadow: none;
.text {
font-size: 1.2em;
line-height: 1.25em;
text-align: center;
display: block;
font-family: "Open Sans", sans-serif;
font-weight: normal;
border-left: 1px solid rgba(0, 0, 0, 0.15);
margin-left: 3em; /* 36 px */
margin-top: 0.25em; /* 3px */
}
&:hover,
&:active,
&:focus {
text-decoration: none;
background-image: none;
}
&:hover {
background-image: linear-gradient(rgba(0, 0, 0, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);
}
&:before {
margin: 0;
padding: 0;
box-shadow: none;
border: none;
width: 3em; /* 36px */
}
}
.zocial.facebook:before {
width: 2.66666666666667em; /* 32px */
}
/* Register page */
.rcue-login-register.register {
label {
width: 7.5em; /* 105px */
}
input[type="text"],
input[type="email"],
input[type="password"] {
width: 22.9090909090909em; /* 252px */
}
form > div.aside-btn {
margin-left: 9.54545454545454em; /* 105px */
width: 12.5454545454546em; /* 138px */
p {
line-height: 1.3em;
}
}
}
/* Customer login */
.rcue-login-register.customer {
background-image: url("img/customer-login-screen-bg2.jpg");
p.powered {
font-size: 1.1em;
margin-top: 1.27272727272727em;
text-align: right;
margin-right: 5.81818181818182em;
a {
color: #666;
&:hover {
color: #0099D3;
}
}
}
}

View file

@ -5,12 +5,6 @@
<meta charset="utf-8">
<title>Keycloak</title>
<style type="text/css">
#idletimeout { background:#CC5100; border:3px solid #FF6500; color:#fff; font-family:arial, sans-serif; text-align:center; font-size:12px; padding:10px; position:relative; top:0px; left:0; right:0; z-index:100000; display:none; }
#idletimeout a { color:#fff; font-weight:bold }
#idletimeout span { font-weight:bold }
</style>
<link rel="icon" href="/auth-server/admin-ui/img/favicon.ico">
<!-- Frameworks -->
@ -59,7 +53,7 @@
<body class="admin-console" data-ng-controller="GlobalCtrl">
<div id="idletimeout">
You will be logged off in <span></span>&nbsp;seconds due to inactivity.
You will be logged off in <strong><span></span> seconds</strong> due to inactivity.
<a id="idletimeout-resume" href="#">Click here to continue using this web page</a>.
</div>

View file

@ -6,7 +6,6 @@ var resourceRequests = 0;
module.config([ '$routeProvider', function($routeProvider) {
$routeProvider
.when('/create/realm', {
templateUrl : 'partials/realm-detail.html',
resolve : {
@ -15,7 +14,8 @@ module.config([ '$routeProvider', function($routeProvider) {
}
},
controller : 'RealmDetailCtrl'
}).when('/realms/:realm', {
})
.when('/realms/:realm', {
templateUrl : 'partials/realm-detail.html',
resolve : {
realm : function(RealmLoader) {
@ -23,10 +23,12 @@ module.config([ '$routeProvider', function($routeProvider) {
}
},
controller : 'RealmDetailCtrl'
}).when('/realms', {
})
.when('/realms', {
templateUrl : 'partials/realm-list.html',
controller : 'RealmListCtrl'
}).when('/realms/:realm/token-settings', {
})
.when('/realms/:realm/token-settings', {
templateUrl : 'partials/realm-tokens.html',
resolve : {
realm : function(RealmLoader) {
@ -34,7 +36,8 @@ module.config([ '$routeProvider', function($routeProvider) {
}
},
controller : 'RealmTokenDetailCtrl'
}).when('/realms/:realm/social-settings', {
})
.when('/realms/:realm/social-settings', {
templateUrl : 'partials/realm-social.html',
resolve : {
realm : function(RealmLoader) {
@ -52,6 +55,15 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'RealmRequiredCredentialsCtrl'
})
.when('/realms/:realm/smtp-settings', {
templateUrl : 'partials/realm-smtp.html',
resolve : {
realm : function(RealmLoader) {
return RealmLoader();
}
},
controller : 'RealmSMTPSettingsCtrl'
})
.when('/create/user/:realm', {
templateUrl : 'partials/user-detail.html',
resolve : {
@ -63,7 +75,8 @@ module.config([ '$routeProvider', function($routeProvider) {
}
},
controller : 'UserDetailCtrl'
}).when('/realms/:realm/users/:user', {
})
.when('/realms/:realm/users/:user', {
templateUrl : 'partials/user-detail.html',
resolve : {
realm : function(RealmLoader) {
@ -74,7 +87,8 @@ module.config([ '$routeProvider', function($routeProvider) {
}
},
controller : 'UserDetailCtrl'
}).when('/realms/:realm/users/:user/role-mappings', {
})
.when('/realms/:realm/users/:user/role-mappings', {
templateUrl : 'partials/role-mappings.html',
resolve : {
realm : function(RealmLoader) {
@ -91,7 +105,8 @@ module.config([ '$routeProvider', function($routeProvider) {
}
},
controller : 'UserRoleMappingCtrl'
}).when('/realms/:realm/users', {
})
.when('/realms/:realm/users', {
templateUrl : 'partials/user-list.html',
resolve : {
realm : function(RealmLoader) {
@ -112,7 +127,8 @@ module.config([ '$routeProvider', function($routeProvider) {
}
},
controller : 'RoleDetailCtrl'
}).when('/realms/:realm/roles/:role', {
})
.when('/realms/:realm/roles/:role', {
templateUrl : 'partials/role-detail.html',
resolve : {
realm : function(RealmLoader) {
@ -123,7 +139,8 @@ module.config([ '$routeProvider', function($routeProvider) {
}
},
controller : 'RoleDetailCtrl'
}).when('/realms/:realm/roles', {
})
.when('/realms/:realm/roles', {
templateUrl : 'partials/role-list.html',
resolve : {
realm : function(RealmLoader) {
@ -150,7 +167,8 @@ module.config([ '$routeProvider', function($routeProvider) {
}
},
controller : 'ApplicationRoleDetailCtrl'
}).when('/realms/:realm/applications/:application/roles/:role', {
})
.when('/realms/:realm/applications/:application/roles/:role', {
templateUrl : 'partials/application-role-detail.html',
resolve : {
realm : function(RealmLoader) {
@ -164,7 +182,8 @@ module.config([ '$routeProvider', function($routeProvider) {
}
},
controller : 'ApplicationRoleDetailCtrl'
}).when('/realms/:realm/applications/:application/credentials', {
})
.when('/realms/:realm/applications/:application/credentials', {
templateUrl : 'partials/application-credentials.html',
resolve : {
realm : function(RealmLoader) {
@ -175,7 +194,8 @@ module.config([ '$routeProvider', function($routeProvider) {
}
},
controller : 'ApplicationCredentialsCtrl'
}).when('/realms/:realm/applications/:application/roles', {
})
.when('/realms/:realm/applications/:application/roles', {
templateUrl : 'partials/application-role-list.html',
resolve : {
realm : function(RealmLoader) {
@ -189,7 +209,8 @@ module.config([ '$routeProvider', function($routeProvider) {
}
},
controller : 'ApplicationRoleListCtrl'
}).when('/realms/:realm/applications/:application/scope-mappings', {
})
.when('/realms/:realm/applications/:application/scope-mappings', {
templateUrl : 'partials/application-scope-mappings.html',
resolve : {
realm : function(RealmLoader) {
@ -208,8 +229,6 @@ module.config([ '$routeProvider', function($routeProvider) {
controller : 'ApplicationScopeMappingCtrl'
})
.when('/create/application/:realm', {
templateUrl : 'partials/application-detail.html',
resolve : {
@ -224,7 +243,8 @@ module.config([ '$routeProvider', function($routeProvider) {
}
},
controller : 'ApplicationDetailCtrl'
}).when('/realms/:realm/applications/:application', {
})
.when('/realms/:realm/applications/:application', {
templateUrl : 'partials/application-detail.html',
resolve : {
realm : function(RealmLoader) {
@ -238,7 +258,8 @@ module.config([ '$routeProvider', function($routeProvider) {
}
},
controller : 'ApplicationDetailCtrl'
}).when('/realms/:realm/applications', {
})
.when('/realms/:realm/applications', {
templateUrl : 'partials/application-list.html',
resolve : {
realm : function(RealmLoader) {

View file

@ -453,3 +453,38 @@ module.controller('RoleDetailCtrl', function($scope, realm, role, Role, $locatio
});
};
});
module.controller('RealmSMTPSettingsCtrl', function($scope, Current, Realm, realm, $http, $location, Dialog, Notifications) {
$scope.realm = {
id : realm.id, realm : realm.realm, social : realm.social,
smtpServer: realm.smtpServer
};
var oldCopy = angular.copy($scope.realm);
$scope.changed = false;
$scope.$watch('realm', function() {
if (!angular.equals($scope.realm, oldCopy)) {
$scope.changed = true;
}
}, true);
$scope.save = function() {
if ($scope.realmForm.$valid) {
var realmCopy = angular.copy($scope.realm);
$scope.changed = false;
Realm.update(realmCopy, function () {
$location.url("/realms/" + realm.id + "/smtp-settings");
Notifications.success("Your changes have been saved to the realm.");
});
} else {
$scope.realmForm.showErrors = true;
}
};
$scope.reset = function() {
$scope.realm = angular.copy(oldCopy);
$scope.changed = false;
$scope.realmForm.showErrors = false;
};
});

View file

@ -16,15 +16,16 @@ module.service('Auth', function() {
module.service('Dialog', function($dialog) {
var dialog = {};
dialog.confirmDelete = function(name, type, success) {
var title = 'Delete ' + name;
var msg = 'Are you sure you want to permanently delete this ' + type + '?';
var title = 'Delete ' + type.charAt(0).toUpperCase() + type.slice(1);
var msg = '<p class="primary">Are you sure you want to permanently delete the ' + type + ' "' + name + '"?</p>' +
'<p>This action can\'t be undone.</p>';
var btns = [ {
result : 'cancel',
label : 'Cancel'
}, {
result : 'ok',
label : 'Delete this ' + type,
cssClass : 'btn-primary'
label : 'Delete',
cssClass : 'destructive'
} ];
$dialog.messageBox(title, msg, btns).open().then(function(result) {

View file

@ -3004,10 +3004,10 @@ angular.module("template/dialog/message.html", []).run(["$templateCache", functi
" <h3>{{ title }}</h3>\n" +
"</div>\n" +
"<div class=\"modal-body\">\n" +
" <p>{{ message }}</p>\n" +
" <p ng-bind-html-unsafe=\"message\"></p>\n" +
"</div>\n" +
"<div class=\"modal-footer\">\n" +
" <button ng-repeat=\"btn in buttons\" ng-click=\"close(btn.result)\" class=\"btn\" ng-class=\"btn.cssClass\">{{ btn.label }}</button>\n" +
" <button ng-repeat=\"btn in buttons\" ng-click=\"close(btn.result)\" class=\"\" ng-class=\"btn.cssClass\">{{ btn.label }}</button> \n" +
"</div>\n" +
"</div>\n" +
"</div>\n" +

View file

@ -1,7 +1,7 @@
<div class="header rcue">
<div class="navbar utility">
<div class="navbar-inner clearfix container">
<h1><a href="#"><strong>Keycloak</strong> Central Login</a></h1>
<h1><a href="#/realms/{{realm.id}}"><strong>Keycloak</strong> Central Login</a></h1>
<ul class="nav pull-right" data-ng-hide="auth.loggedIn">
<li><a href="/auth-server/rest/saas/login">Login</a></li>
<li><a href="/auth-server/rest/saas/registrations">Register</a></li>

View file

@ -9,6 +9,7 @@
<li><a href="#/realms/{{realm.id}}/roles">Roles</a></li>
<li class="active"><a href="#/realms/{{realm.id}}/required-credentials">Credentials</a></li>
<li><a href="#/realms/{{realm.id}}/token-settings">Token</a></li>
<li><a href="#/realms/{{realm.id}}/smtp-settings">SMTP</a></li>
</ul>
</div>
<div id="content">

View file

@ -9,6 +9,7 @@
<li><a href="#/realms/{{realm.id}}/roles">Roles</a></li>
<li><a href="#/realms/{{realm.id}}/required-credentials">Credentials</a></li>
<li><a href="#/realms/{{realm.id}}/token-settings">Token</a></li>
<li><a href="#/realms/{{realm.id}}/smtp-settings">SMTP</a></li>
</ul>
</div>
<div id="content">
@ -75,6 +76,32 @@
</label>
</div>
</div>
<div class="form-group clearfix block">
<label for="resetPasswordAllowed" class="control-label">Reset password</label>
<div class="onoffswitch">
<input type="checkbox" data-ng-model="realm.resetPasswordAllowed" class="onoffswitch-checkbox" name="resetPasswordAllowed" id="resetPasswordAllowed">
<label for="resetPasswordAllowed" class="onoffswitch-label">
<span class="onoffswitch-inner">
<span class="onoffswitch-active">ON</span>
<span class="onoffswitch-inactive">OFF</span>
</span>
<span class="onoffswitch-switch"></span>
</label>
</div>
</div>
<div class="form-group clearfix block">
<label for="verifyEmail" class="control-label">Verify email</label>
<div class="onoffswitch">
<input type="checkbox" data-ng-model="realm.verifyEmail" class="onoffswitch-checkbox" name="verifyEmail" id="verifyEmail">
<label for="verifyEmail" class="onoffswitch-label">
<span class="onoffswitch-inner">
<span class="onoffswitch-active">ON</span>
<span class="onoffswitch-inactive">OFF</span>
</span>
<span class="onoffswitch-switch"></span>
</label>
</div>
</div>
<div class="form-group clearfix block">
<label for="accountManagement" class="control-label two-lines">User account management</label>
<div class="onoffswitch">

View file

@ -0,0 +1,134 @@
<div id="wrapper" class="container">
<div class="row">
<div class="bs-sidebar col-md-3 clearfix" data-ng-include data-src="'partials/realm-menu.html'"></div>
<div id="content-area" class="col-md-9" role="main">
<div class="top-nav" data-ng-hide="createRealm">
<ul class="rcue-tabs">
<li><a href="#/realms/{{realm.id}}">General</a></li>
<li data-ng-show="realm.social"><a href="#/realms/{{realm.id}}/social-settings">Social</a></li>
<li><a href="#/realms/{{realm.id}}/roles">Roles</a></li>
<li><a href="#/realms/{{realm.id}}/required-credentials">Credentials</a></li>
<li><a href="#/realms/{{realm.id}}/token-settings">Token</a></li>
<li class="active"><a href="#/realms/{{realm.id}}/smtp-settings">SMTP</a></li>
</ul>
</div>
<div id="content">
<ol class="breadcrumb">
<li><a href="#/realms/{{realm.id}}">{{realm.realm}}</a></li>
<li><a href="#/realms/{{realm.id}}">Settings</a></li>
<li class="active">SMTP Configuration</li>
</ol>
<h2><span>{{realm.realm}}</span> SMTP Settings</h2>
<form name="realmForm" novalidate>
<fieldset>
<legend uncollapsed><span class="text">Required Settings</span></legend>
<div class="form-group clearfix">
<label for="smtpHost" class="control-label">Host <span class="required">*</span></label>
<div class="controls">
<input id="smtpHost" type="text" ng-model="realm.smtpServer.host" placeholder="SMTP Host" required>
</div>
</div>
<div class="form-group clearfix">
<label for="smtpPort" class="control-label">Port <span class="required">*</span></label>
<div class="controls">
<input id="smtpPort" type="text" ng-model="realm.smtpServer.port" placeholder="SMTP Port (defaults to 25)" required>
</div>
</div>
<div class="form-group clearfix">
<label for="smtpFrom" class="control-label">From <span class="required">*</span></label>
<div class="controls">
<input id="smtpFrom" type="email" ng-model="realm.smtpServer.from" placeholder="SMTP From" required>
</div>
</div>
<div class="form-group clearfix">
<label for="smtpSSL" class="control-label">Enable SSL</label>
<div class="onoffswitch">
<input type="checkbox" data-ng-model="realm.smtpServer.ssl" class="onoffswitch-checkbox" name="smtpSSL" id="smtpSSL">
<label for="smtpSSL" class="onoffswitch-label">
<span class="onoffswitch-inner">
<span class="onoffswitch-active">ON</span>
<span class="onoffswitch-inactive">OFF</span>
</span>
<span class="onoffswitch-switch"></span>
</label>
</div>
</div>
<div class="form-group clearfix">
<label for="smtpStartTLS" class="control-label">Enable StartTLS</label>
<div class="onoffswitch">
<input type="checkbox" data-ng-model="realm.smtpServer.starttls" class="onoffswitch-checkbox" name="smtpStartTLS" id="smtpStartTLS">
<label for="smtpStartTLS" class="onoffswitch-label">
<span class="onoffswitch-inner">
<span class="onoffswitch-active">ON</span>
<span class="onoffswitch-inactive">OFF</span>
</span>
<span class="onoffswitch-switch"></span>
</label>
</div>
</div>
</fieldset>
<fieldset>
<legend collapsed><span class="text">Authentication</span></legend>
<div class="form-group clearfix">
<label for="smtpAuth" class="control-label">Enabled</label>
<div class="onoffswitch">
<input type="checkbox" data-ng-model="realm.smtpServer.auth" class="onoffswitch-checkbox" name="smtpAuth" id="smtpAuth">
<label for="smtpAuth" class="onoffswitch-label">
<span class="onoffswitch-inner">
<span class="onoffswitch-active">ON</span>
<span class="onoffswitch-inactive">OFF</span>
</span>
<span class="onoffswitch-switch"></span>
</label>
</div>
</div>
<div class="form-group clearfix">
<label for="smtpUsername" class="control-label">Username <span class="required" ng-show="realm.smtpServer.auth">*</span></label>
<div class="controls">
<input id="smtpUsername" type="text" ng-model="realm.smtpServer.user" placeholder="Login Username" ng-disabled="!realm.smtpServer.auth" ng-required="realm.smtpServer.auth">
</div>
</div>
<div class="form-group clearfix">
<label for="smtpPassword" class="control-label">Password <span class="required" ng-show="realm.smtpServer.auth">*</span></label>
<div class="controls">
<input id="smtpPassword" type="password" ng-model="realm.smtpServer.password" placeholder="Login Password" ng-disabled="!realm.smtpServer.auth" ng-required="realm.smtpServer.auth">
</div>
</div>
</fieldset>
<!--
<fieldset class="border-top">
<div class="form-group clearfix" ng-repeat="(name, setting) in smtpSettings | orderBy: setting.required">
<label for="{{name}}" class="control-label">{{name.replace('mail.smtp.','')}} <span class="required" data-ng-show="setting.required">*</span></label>
<div ng-show="setting.type == 'boolean'" class="onoffswitch">
<input type="checkbox" data-ng-model="realm.smtp[name]" class="onoffswitch-checkbox" name="{{name}}" id="{{name}}">
<label for="{{name}}" class="onoffswitch-label">
<span class="onoffswitch-inner">
<span class="onoffswitch-active">ON</span>
<span class="onoffswitch-inactive">OFF</span>
</span>
<span class="onoffswitch-switch"></span>
</label>
</div>
<div ng-show="setting.type == 'String'" class="controls">
<input id="{{name}}" type="text" ng-model="realm.smtp[name]" placeholder="SMTP {{name}} / {{ setting.required }}">
</div>
<div ng-show="setting.type == 'int'" class="controls">
<input id="{{name}}" type="number" ng-model="realm.smtp[name]" placeholder="SMTP {{name}} / {{ setting.required }}">
</div>
</div>
</fieldset>
-->
<div class="form-actions">
<button type="submit" data-ng-click="save()" class="primary" data-ng-show="changed">Save
</button>
<button type="submit" data-ng-click="reset()" data-ng-show="changed">Clear changes
</button>
</div>
</form>
</div>
</div>
<div id="container-right-bg"></div>
</div>
</div>

View file

@ -9,6 +9,7 @@
<li><a href="#/realms/{{realm.id}}/roles">Roles</a></li>
<li><a href="#/realms/{{realm.id}}/required-credentials">Credentials</a></li>
<li><a href="#/realms/{{realm.id}}/token-settings">Token</a></li>
<li><a href="#/realms/{{realm.id}}/smtp-settings">SMTP</a></li>
</ul>
</div>
<div id="content">
@ -92,7 +93,7 @@
<div ng-include src="'partials/provider/'+ helpPId +'-help.html'"></div>
</div>
<div class="modal-footer">
<button class="btn" ng-click="closeHelp()">Close</button>
<button ng-click="closeHelp()">Close</button>
</div>
</div>
</div>

View file

@ -9,6 +9,7 @@
<li><a href="#/realms/{{realm.id}}/roles">Roles</a></li>
<li><a href="#/realms/{{realm.id}}/required-credentials">Credentials</a></li>
<li class="active"><a href="#/realms/{{realm.id}}/token-settings">Token</a></li>
<li><a href="#/realms/{{realm.id}}/smtp-settings">SMTP</a></li>
</ul>
</div>
<div id="content">
@ -49,7 +50,7 @@
</div>
</div>
<div class="form-group input-select">
<label for="accessCodeLifespanUserAction">Access code user action lifespan</label>
<label for="accessCodeLifespanUserAction" class="two-lines">Access code user action lifespan</label>
<div class="input-group">
<input type="text" data-ng-model="realm.accessCodeLifespanUserAction" id="accessCodeLifespanUserAction" name="accessCodeLifespanUserAction" class="tiny">
<div class="select-rcue">

View file

@ -9,6 +9,7 @@
<li class="active"><a href="#/realms/{{realm.id}}/roles">Roles</a></li>
<li><a href="#/realms/{{realm.id}}/required-credentials">Credentials</a></li>
<li><a href="#/realms/{{realm.id}}/token-settings">Token</a></li>
<li><a href="#/realms/{{realm.id}}/smtp-settings">SMTP</a></li>
</ul>
</div>
<div id="content">

View file

@ -9,6 +9,7 @@
<li class="active"><a href="#/realms/{{realm.id}}/roles">Roles</a></li>
<li><a href="#/realms/{{realm.id}}/required-credentials">Credentials</a></li>
<li><a href="#/realms/{{realm.id}}/token-settings">Token</a></li>
<li><a href="#/realms/{{realm.id}}/smtp-settings">SMTP</a></li>
</ul>
</div>
<div id="content">

View file

@ -18,6 +18,7 @@ public class ApplicationRepresentation {
protected boolean enabled;
protected List<CredentialRepresentation> credentials;
protected List<RoleRepresentation> roles;
protected String[] defaultRoles;
protected List<UserRoleMappingRepresentation> roleMappings;
protected List<ScopeMappingRepresentation> scopeMappings;
protected List<String> redirectUris;
@ -164,4 +165,12 @@ public class ApplicationRepresentation {
public void setWebOrigins(List<String> webOrigins) {
this.webOrigins = webOrigins;
}
public String[] getDefaultRoles() {
return defaultRoles;
}
public void setDefaultRoles(String[] defaultRoles) {
this.defaultRoles = defaultRoles;
}
}

View file

@ -28,7 +28,7 @@ public class RealmRepresentation {
protected String privateKey;
protected String publicKey;
protected List<RoleRepresentation> roles;
protected String[] defaultRoles;
protected List<String> defaultRoles;
protected Set<String> requiredCredentials;
protected Set<String> requiredApplicationCredentials;
protected Set<String> requiredOAuthClientCredentials;
@ -220,11 +220,11 @@ public class RealmRepresentation {
this.roles = roles;
}
public String[] getDefaultRoles() {
public List<String> getDefaultRoles() {
return defaultRoles;
}
public void setDefaultRoles(String[] defaultRoles) {
public void setDefaultRoles(List<String> defaultRoles) {
this.defaultRoles = defaultRoles;
}

50
dist/assembly.xml vendored Normal file
View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<assembly xmlns="urn:maven:assembly:1.1.0-SNAPSHOT">
<id>distro</id>
<formats>
<format>zip</format>
<format>tar.gz</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>${build.target.dir}</directory>
<outputDirectory>keycloak-${project.version}</outputDirectory>
<excludes>
<exclude>**/*.sh</exclude>
<exclude>domain/tmp/auth</exclude>
<exclude>domain/tmp/auth</exclude>
<exclude>**/*-users.properties</exclude>
</excludes>
</fileSet>
<fileSet>
<directory>${build.target.dir}</directory>
<outputDirectory>keycloak-${project.version}</outputDirectory>
<includes>
<include>**/*.sh</include>
</includes>
<fileMode>0755</fileMode>
</fileSet>
<fileSet>
<directory>${build.target.dir}</directory>
<outputDirectory>keycloak-${project.version}</outputDirectory>
<includes>
<include>**/*-users.properties</include>
</includes>
<fileMode>0600</fileMode>
</fileSet>
<fileSet>
<directory>${build.target.dir}</directory>
<outputDirectory>keycloak-${project.version}</outputDirectory>
<includes>
<include>domain/tmp/auth</include>
<include>standalone/tmp/auth</include>
</includes>
<directoryMode>0700</directoryMode>
</fileSet>
</fileSets>
</assembly>

37
dist/build.xml vendored Normal file
View file

@ -0,0 +1,37 @@
<project name="keycloak-dist" basedir=".">
<target name="jboss">
<unzip src="${org.jboss.as:jboss-as-dist:zip}" dest="${project.build.directory}"/>
<chmod perm="755">
<fileset dir="${project.build.directory}/jboss-as-${jboss.version}/bin">
<include name="**/*.sh"/>
</fileset>
</chmod>
<move todir="${build.target.dir}" overwrite="true">
<fileset dir="${project.build.directory}/jboss-as-${jboss.version}">
<include name="**/*"/>
</fileset>
</move>
<delete dir="${project.build.directory}/jboss-as-${jboss.version}"/>
</target>
<target name="resteasy-modules">
<get src="http://sourceforge.net/projects/resteasy/files/Resteasy%20JAX-RS/${resteasy.version}/resteasy-jaxrs-${resteasy.version}-all.zip"
dest="${project.build.directory}" skipexisting="true"/>
<unzip src="${project.build.directory}/resteasy-jaxrs-${resteasy.version}-all.zip"
dest="${project.build.directory}">
<patternset>
<include name="resteasy-jaxrs-${resteasy.version}/resteasy-jboss-modules-${resteasy.version}.zip"/>
</patternset>
<mapper type="flatten"/>
</unzip>
<unzip src="${project.build.directory}/resteasy-jboss-modules-${resteasy.version}.zip"
dest="${build.target.dir}/modules"/>
</target>
<target name="keycloak-server">
<copy file="${org.keycloak:keycloak-server:war}"
tofile="${build.target.dir}/standalone/deployments/auth-server.war" overwrite="true"/>
</target>
<target name="all" depends="jboss, resteasy-modules, keycloak-server"/>
</project>

96
dist/pom.xml vendored Normal file
View file

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-parent</artifactId>
<version>1.0-alpha-1</version>
</parent>
<artifactId>keycloak-dist</artifactId>
<name>Keycloak Dist</name>
<packaging>pom</packaging>
<properties>
<build.target.dir>${project.build.directory}/keycloak-${project.version}</build.target.dir>
</properties>
<profiles>
<profile>
<id>release</id>
<activation>
<property>
<name>release</name>
<value>true</value>
</property>
</activation>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server</artifactId>
<version>1.0-alpha-1</version>
<type>war</type>
</dependency>
<dependency>
<groupId>org.jboss.as</groupId>
<artifactId>jboss-as-dist</artifactId>
<version>${jboss.version}</version>
<type>zip</type>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.7</version>
<executions>
<execution>
<id>build</id>
<phase>compile</phase>
<configuration>
<target>
<ant antfile="build.xml" inheritRefs="false">
<target name="all"/>
</ant>
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>assemble</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>assembly.xml</descriptor>
</descriptors>
<finalName>keycloak-${project.version}</finalName>
<appendAssemblyId>false</appendAssemblyId>
<outputDirectory>target/</outputDirectory>
<workDirectory>target/assembly/work</workDirectory>
<tarLongFileMode>gnu</tarLongFileMode>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View file

@ -1,24 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<script src="keycloak.js"></script>
</head>
<body>
<script>
keycloak.init({
clientId : '57572475438.apps.googleusercontent.com',
clientSecret : 'xyfsPS9maRTz5fj0pOxf0zjD'
});
if (keycloak.authenticated) {
document.write('<h2>Token</h2><pre>' + keycloak.token + '</pre>');
document.write('<h2>Token info</h2><pre>' + JSON.stringify(keycloak.tokenInfo, undefined, 4) + '</pre>');
document.write('<h2>Profile</h2><pre>' + JSON.stringify(keycloak.profile(true), undefined, 4) + '</pre>');
document.write('<h2>Contacts</h2><pre>' + keycloak.contacts(true) + '</pre>');
} else {
document.write('<a href="#" id="login" onclick="keycloak.login()">Login</a>');
}
</script>
</body>
</html>

View file

@ -1,139 +0,0 @@
window.keycloak = (function () {
var kc = {};
var config = {
clientId: null,
clientSecret: null
};
kc.init = function (c) {
for (var prop in config) {
if (c[prop]) {
config[prop] = c[prop];
}
if (!config[prop]) {
throw new Error(prop + ' not defined');
}
}
loadToken();
if (kc.token) {
kc.user = kc.tokenInfo.user_id;
kc.authenticated = true;
} else {
kc.authenticated = false;
kc.user = null;
}
}
kc.login = function () {
var clientId = encodeURIComponent(config.clientId);
var redirectUri = encodeURIComponent(window.location.href);
var state = encodeURIComponent(createUUID());
var scope = encodeURIComponent('https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/plus.login');
var url = 'https://accounts.google.com/o/oauth2/auth?response_type=token&client_id=' + clientId + '&redirect_uri=' + redirectUri
+ '&state=' + state + '&scope=' + scope;
sessionStorage.state = state;
window.location.href = url;
}
function parseToken(token) {
return JSON.parse(atob(token.split('.')[1]));
}
kc.profile = function(header) {
var url = 'https://www.googleapis.com/oauth2/v1/userinfo'
if (!header) {
url = url + '?access_token=' + kc.token;
}
var http = new XMLHttpRequest();
http.open('GET', url, false);
if (header) {
http.setRequestHeader('Authorization', 'Bearer ' + kc.token);
}
http.send();
if (http.status == 200) {
return JSON.parse(http.responseText);
}
}
kc.contacts = function(header) {
var url = 'https://www.googleapis.com/plus/v1/people/me';
if (!header) {
url = url + '?access_token=' + kc.token;
}
var http = new XMLHttpRequest();
http.open('GET', url, false);
if (header) {
http.setRequestHeader('Authorization', 'Bearer ' + kc.token);
}
http.send();
if (http.status == 200) {
return http.responseText;
}
}
return kc;
function loadToken() {
var params = {}
var queryString = location.hash.substring(1)
var regex = /([^&=]+)=([^&]*)/g, m;
while (m = regex.exec(queryString)) {
params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
}
var token = params['access_token'];
var state = params['state'];
if (token && state === sessionStorage.state) {
window.history.replaceState({}, document.title, location.protocol + "//" + location.host + location.pathname);
kc.token = token;
var url = 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=' + token;
var http = new XMLHttpRequest();
http.open('GET', url, false);
http.send();
if (http.status == 200) {
kc.tokenInfo = JSON.parse(http.responseText);
}
}
return undefined;
}
function getQueryParam(name) {
console.debug(window.location.hash);
var params = window.location.hash.substring(1).split('&');
for (var i = 0; i < params.length; i++) {
var p = params[i].split('=');
if (decodeURIComponent(p[0]) == name) {
return p[1];
}
}
}
function createUUID() {
var s = [];
var hexDigits = '0123456789abcdef';
for (var i = 0; i < 36; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
}
s[14] = '4';
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
s[8] = s[13] = s[18] = s[23] = '-';
var uuid = s.join('');
return uuid;
}
})();

View file

@ -1,222 +0,0 @@
<<<<<<< Updated upstream
window.keycloak = (function() {
var kc = {};
var config = null;
kc.init = function(c) {
config = c;
var token = getTokenFromCode();
if (token) {
var t = parseToken(token);
kc.user = t.prn;
kc.authenticated = true;
} else {
kc.authenticated = false;
}
}
kc.login = function() {
var clientId = encodeURIComponent(config.clientId);
var redirectUri = encodeURIComponent(window.location.href);
var state = encodeURIComponent(createUUID());
var realm = encodeURIComponent(config.realm);
var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/login?response_type=code&client_id=' + clientId + '&redirect_uri=' + redirectUri
+ '&state=' + state;
window.location.href = url;
}
return kc;
function parseToken(token) {
return JSON.parse(atob(token.split('.')[1]));
}
function getTokenFromCode() {
var code = getQueryParam('code');
if (code) {
window.history.replaceState({}, document.title, location.protocol + "//" + location.host + location.pathname);
var clientId = encodeURIComponent(config.clientId);
var clientSecret = encodeURIComponent(config.clientSecret);
var realm = encodeURIComponent(config.realm);
var params = 'code=' + code + '&client_id=' + config.clientId + '&password=' + config.clientSecret;
var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/access/codes'
var http = new XMLHttpRequest();
http.open('POST', url, false);
http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
http.send(params);
if (http.status == 200) {
return JSON.parse(http.responseText)['access_token'];
}
}
return undefined;
}
function getQueryParam(name) {
var params = window.location.search.substring(1).split('&');
for ( var i = 0; i < params.length; i++) {
var p = params[i].split('=');
if (decodeURIComponent(p[0]) == name) {
return p[1];
}
}
}
function createUUID() {
var s = [];
var hexDigits = '0123456789abcdef';
for ( var i = 0; i < 36; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
}
s[14] = '4';
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
s[8] = s[13] = s[18] = s[23] = '-';
var uuid = s.join('');
return uuid;
}
=======
window.keycloak = (function () {
var kc = {};
var config = {
baseUrl : null,
clientId : null,
clientSecret: null,
realm: null
};
kc.init = function (c) {
for (var prop in config) {
if (c[prop]) {
config[prop] = c[prop];
}
if (!config[prop]) {
throw new Error(prop + 'not defined');
}
}
var token = getTokenFromCode();
if (token) {
var t = parseToken(token);
kc.user = t.prn;
kc.authenticated = true;
} else {
kc.authenticated = false;
}
}
kc.login = function () {
var clientId = encodeURIComponent(config.clientId);
var redirectUri = encodeURIComponent(window.location.href);
var realm = encodeURIComponent(config.realm);
var state = encodeURIComponent(createUUID());
var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/login?response_type=code&client_id=' + clientId + '&redirect_uri=' + redirectUri
+ '&state=' + state;
sessionStorage.state = state;
window.location.href = url;
}
return kc;
function parseToken(token) {
var t = base64Decode(token.split('.')[1]);
return JSON.parse(t);
}
function getTokenFromCode() {
var code = getQueryParam('code');
var state = getQueryParam('state');
if (code) {
if (state && state === sessionStorage.state) {
window.history.replaceState({}, document.title, location.protocol + "//" + location.host + location.pathname);
var clientId = encodeURIComponent(config.clientId);
var clientSecret = encodeURIComponent(config.clientSecret);
var realm = encodeURIComponent(config.realm);
var params = 'code=' + code + '&client_id=' + clientId + '&password=' + clientSecret;
var url = config.baseUrl + '/rest/realms/' + realm + '/tokens/access/codes'
var http = new XMLHttpRequest();
http.open('POST', url, false);
http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
http.send(params);
if (http.status == 200) {
return JSON.parse(http.responseText)['access_token'];
}
}
}
return undefined;
}
function getQueryParam(name) {
var params = window.location.search.substring(1).split('&');
for (var i = 0; i < params.length; i++) {
var p = params[i].split('=');
if (decodeURIComponent(p[0]) == name) {
return p[1];
}
}
}
function createUUID() {
var s = [];
var hexDigits = '0123456789abcdef';
for (var i = 0; i < 36; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
}
s[14] = '4';
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
s[8] = s[13] = s[18] = s[23] = '-';
var uuid = s.join('');
return uuid;
}
function base64Decode(data) {
var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0,
ac = 0,
dec = "",
tmp_arr = [];
if (!data) {
return data;
}
data += '';
do {
h1 = b64.indexOf(data.charAt(i++));
h2 = b64.indexOf(data.charAt(i++));
h3 = b64.indexOf(data.charAt(i++));
h4 = b64.indexOf(data.charAt(i++));
bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
o1 = bits >> 16 & 0xff;
o2 = bits >> 8 & 0xff;
o3 = bits & 0xff;
if (h3 == 64) {
tmp_arr[ac++] = String.fromCharCode(o1);
} else if (h4 == 64) {
tmp_arr[ac++] = String.fromCharCode(o1, o2);
} else {
tmp_arr[ac++] = String.fromCharCode(o1, o2, o3);
}
} while (i < data.length);
dec = tmp_arr.join('');
return dec;
}
>>>>>>> Stashed changes
})();

View file

@ -1,13 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<script src="http://code.jquery.com/jquery-2.0.3.js"></script>
</head>
<body>
<script>
$.ajax('https://baas.kinvey.com/appdata/kid_PVD-jo1HqO');
</script>
</body>
</html>

View file

@ -1,60 +0,0 @@
{
"id": "test",
"realm": "test",
"enabled": true,
"tokenLifespan": 300,
"accessCodeLifespan": 10,
"accessCodeLifespanUserAction": 600,
"sslNotRequired": true,
"cookieLoginAllowed": true,
"registrationAllowed": true,
"resetPasswordAllowed": 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" ],
"requiredApplicationCredentials": [ "password" ],
"requiredOAuthClientCredentials": [ "password" ],
"defaultRoles": [ "user" ],
"users" : [
{
"username" : "test-user@localhost",
"enabled": true,
"email" : "test-user@localhost",
"credentials" : [
{ "type" : "password",
"value" : "password" }
]
}
],
"roles": [
{
"name": "user",
"description": "Have User privileges"
},
{
"name": "admin",
"description": "Have Administrator privileges"
}
],
"roleMappings": [
{
"username": "test-user@localhost",
"roles": ["user"]
}
],
"applications": [
{
"name": "test-app",
"enabled": true,
"adminUrl": "http://localhost:8081/app/logout",
"useRealmMappings": true,
"webOrigins": [ "http://localhost", "http://localhost:8000", "http://localhost:8080" ],
"credentials": [
{
"type": "password",
"value": "password"
}
]
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 B

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

View file

@ -49,13 +49,30 @@ body {
width: 100%;
min-width: 120em;
}
.rcue-login-register .form-area.separator,
.rcue-login-register .form-area.social,
.rcue-login-register .form-area.social.separator {
background-repeat: no-repeat;
background-position: 42.7em center;
}
.rcue-login-register .form-area.separator {
background-image: url(img/login-register-separator.png);
background-position: 43.2em center;
}
.rcue-login-register .form-area.social {
background-image: url(img/login-register-social.png);
}
.rcue-login-register .form-area.social.separator {
background-image: url(img/login-register-social-separator.png);
}
.rcue-login-register .background-area .section {
float: left;
padding: 0 4.5em 0 4.6em;
width: auto;
position: relative;
}
.rcue-login-register .background-area .separator .section {
.rcue-login-register .background-area .separator .section,
.rcue-login-register .background-area .social .section {
padding-top: 1.5em;
padding-bottom: 1.5em;
}
@ -65,15 +82,6 @@ body {
.rcue-login-register .background-area .section:first-child {
padding-right: 4.5em;
}
.rcue-login-register .form-area.separator {
background-image: url(img/login-register-separator.png);
background-repeat: no-repeat;
background-position: 43.2em center;
}
.rcue-login-register .form-area.social {
background-image: url(img/login-register-social-separators.png);
background-position: 39.6em center;
}
.rcue-login-register .section > p {
font-size: 1.3em;
margin-bottom: 1.53846153846154em;
@ -120,7 +128,6 @@ body {
.rcue-login-register form > div.aside-btn input[type="checkbox"] {
margin-bottom: 0.54545454545455em;
/* 6px */
}
.rcue-login-register form > input[type="button"],
.rcue-login-register form > input[type="submit"]{
@ -137,13 +144,10 @@ body {
top: -0.636363636363636em;
}
.rcue-login-register .feedback.bottom-left {
left: 32.7em;
left: 35.7em;
top: -9.2em;
min-width: 35em;
}
.rcue-login-register.reset .feedback.bottom-left {
left: 35.7em;
}
.rcue-login-register input.error[type="text"],
.rcue-login-register input.error[type="password"],
.rcue-login-register input.error[type="email"] {

View file

@ -18,18 +18,20 @@
<span>${role.description}</span>
</li>
</#list>
</ul>
<#list oauth.resourceRolesRequested?keys as resourceRole>
<p class="instruction"><strong>${resourceRole}</strong> requests access to:</p>
<li>
<strong>${resourceRole}</strong>
<ul>
<#list oauth.resourceRolesRequested[resourceRole] as role>
<li>
<span>${role.description}</span>
<span><#if role.description??>${role.description}<#else>${role.name}</#if></span>
</li>
</#list>
</ul>
</li>
</#list>
</ul>
<p class="terms">Keycloak Central Login and Google will use this information in accordance with their respective terms of service and privacy policies.</p>
<form class="form-actions" action="${oauth.action}" method="POST">

View file

@ -1,5 +1,7 @@
package org.keycloak.models;
import java.util.List;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
@ -31,4 +33,9 @@ public interface ApplicationModel extends RoleContainerModel, RoleMapperModel, S
void setBaseUrl(String url);
List<String> getDefaultRoles();
void addDefaultRole(String name);
void updateDefaultRoles(String[] defaultRoles);
}

View file

@ -12,5 +12,9 @@ public interface Constants {
String IDENTITY_REQUESTER_ROLE = "KEYCLOAK_IDENTITY_REQUESTER";
String WILDCARD_ROLE = "*";
String ACCOUNT_APPLICATION = "Account";
String ACCOUNT_PROFILE_ROLE = "view-profile";
String ACCOUNT_MANAGE_ROLE = "manage-account";
String ACCOUNT_MANAGEMENT_APPLICATION = "Account Management";
}

View file

@ -84,7 +84,7 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
UserModel addUser(String username);
List<RoleModel> getDefaultRoles();
List<String> getDefaultRoles();
void addDefaultRole(String name);

View file

@ -272,4 +272,61 @@ public class ApplicationAdapter implements ApplicationModel {
return query;
}
@Override
public List<String> getDefaultRoles() {
Collection<RoleEntity> entities = application.getDefaultRoles();
List<String> roles = new ArrayList<String>();
if (entities == null) return roles;
for (RoleEntity entity : entities) {
roles.add(entity.getName());
}
return roles;
}
@Override
public void addDefaultRole(String name) {
RoleModel role = getRole(name);
if (role == null) {
role = addRole(name);
}
Collection<RoleEntity> entities = application.getDefaultRoles();
for (RoleEntity entity : entities) {
if (entity.getId().equals(role.getId())) {
return;
}
}
entities.add(((RoleAdapter) role).getRole());
em.flush();
}
public static boolean contains(String str, String[] array) {
for (String s : array) {
if (str.equals(s)) return true;
}
return false;
}
@Override
public void updateDefaultRoles(String[] defaultRoles) {
Collection<RoleEntity> entities = application.getDefaultRoles();
Set<String> already = new HashSet<String>();
List<RoleEntity> remove = new ArrayList<RoleEntity>();
for (RoleEntity rel : entities) {
if (!contains(rel.getName(), defaultRoles)) {
remove.add(rel);
} else {
already.add(rel.getName());
}
}
for (RoleEntity entity : remove) {
entities.remove(entity);
}
em.flush();
for (String roleName : defaultRoles) {
if (!already.contains(roleName)) {
addDefaultRole(roleName);
}
}
em.flush();
}
}

View file

@ -457,12 +457,12 @@ public class RealmAdapter implements RealmModel {
}
@Override
public List<RoleModel> getDefaultRoles() {
public List<String> getDefaultRoles() {
Collection<RoleEntity> entities = realm.getDefaultRoles();
List<RoleModel> roles = new ArrayList<RoleModel>();
List<String> roles = new ArrayList<String>();
if (entities == null) return roles;
for (RoleEntity entity : entities) {
roles.add(new RoleAdapter(entity));
roles.add(entity.getName());
}
return roles;
}
@ -504,8 +504,8 @@ public class RealmAdapter implements RealmModel {
}
for (RoleEntity entity : remove) {
entities.remove(entity);
em.remove(entity);
}
em.flush();
for (String roleName : defaultRoles) {
if (!already.contains(roleName)) {
addDefaultRole(roleName);
@ -543,6 +543,7 @@ public class RealmAdapter implements RealmModel {
em.persist(user);
applicationData.setApplicationUser(user);
applicationData.setName(name);
applicationData.setEnabled(true);
realm.getApplications().add(applicationData);
em.persist(applicationData);
em.flush();

View file

@ -33,6 +33,10 @@ public class ApplicationEntity {
@JoinTable(name="APPLICATION_ROLES")
Collection<RoleEntity> roles = new ArrayList<RoleEntity>();
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
@JoinTable(name="APPLICATION_DEFAULT_ROLES")
Collection<RoleEntity> defaultRoles = new ArrayList<RoleEntity>();
public String getId() {
return id;
}
@ -84,4 +88,12 @@ public class ApplicationEntity {
public void setName(String name) {
this.name = name;
}
public Collection<RoleEntity> getDefaultRoles() {
return defaultRoles;
}
public void setDefaultRoles(Collection<RoleEntity> defaultRoles) {
this.defaultRoles = defaultRoles;
}
}

View file

@ -281,4 +281,19 @@ public class ApplicationAdapter implements ApplicationModel {
}
return result;
}
@Override
public List<String> getDefaultRoles() {
return null; //To change body of implemented methods use File | Settings | File Templates.
}
@Override
public void addDefaultRole(String name) {
//To change body of implemented methods use File | Settings | File Templates.
}
@Override
public void updateDefaultRoles(String[] defaultRoles) {
//To change body of implemented methods use File | Settings | File Templates.
}
}

View file

@ -15,10 +15,7 @@ import org.picketlink.idm.model.sample.SampleModel;
import org.picketlink.idm.query.IdentityQuery;
import org.picketlink.idm.query.RelationshipQuery;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -257,4 +254,44 @@ public class ApplicationAdapter implements ApplicationModel {
return roles;
}
@Override
public List<String> getDefaultRoles() {
if ( applicationData.getDefaultRoles() != null) {
return Arrays.asList(applicationData.getDefaultRoles());
}
else {
return Collections.emptyList();
}
}
@Override
public void addDefaultRole(String name) {
if (getRole(name) == null) {
addRole(name);
}
String[] defaultRoles = applicationData.getDefaultRoles();
if (defaultRoles == null) {
defaultRoles = new String[1];
} else {
defaultRoles = Arrays.copyOf(defaultRoles, defaultRoles.length + 1);
}
defaultRoles[defaultRoles.length - 1] = name;
applicationData.setDefaultRoles(defaultRoles);
updateApplication();
}
@Override
public void updateDefaultRoles(String[] defaultRoles) {
for (String name : defaultRoles) {
if (getRole(name) == null) {
addRole(name);
}
}
applicationData.setDefaultRoles(defaultRoles);
updateApplication();
}
}

View file

@ -43,13 +43,7 @@ import java.io.StringWriter;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
/**
* Meant to be a per-request object
@ -767,17 +761,15 @@ public class RealmAdapter implements RealmModel {
}
@Override
public List<RoleModel> getDefaultRoles() {
List<RoleModel> defaultRoleModels = new ArrayList<RoleModel>();
if (realm.getDefaultRoles() != null) {
for (String name : realm.getDefaultRoles()) {
RoleAdapter role = getRole(name);
if (role != null) {
defaultRoleModels.add(role);
public List<String> getDefaultRoles() {
if (realm.getDefaultRoles() == null) return Collections.emptyList();
List<String> list = new ArrayList<String>();
for (String role : realm.getDefaultRoles()) {
RoleModel model = getRole(role);
if (model == null) throw new RuntimeException("default role missing");
list.add(role);
}
}
}
return defaultRoleModels;
return list;
}
@Override

View file

@ -15,6 +15,7 @@ public class ApplicationData extends AbstractPartition {
private String managementUrl;
private String baseUrl;
private User resourceUser;
private String[] defaultRoles;
public ApplicationData() {
super(null);
@ -76,4 +77,13 @@ public class ApplicationData extends AbstractPartition {
this.baseUrl = baseUrl;
}
@AttributeProperty
public String[] getDefaultRoles() {
return defaultRoles;
}
public void setDefaultRoles(String[] defaultRoles) {
this.defaultRoles = defaultRoles;
}
}

View file

@ -34,6 +34,9 @@ public class ApplicationEntity implements Serializable {
@AttributeValue
private String baseUrl;
@AttributeValue
private String[] defaultRoles;
@OneToOne
@AttributeValue
AccountTypeEntity resourceUser;
@ -94,4 +97,13 @@ public class ApplicationEntity implements Serializable {
public void setResourceUser(AccountTypeEntity resourceUser) {
this.resourceUser = resourceUser;
}
public String[] getDefaultRoles() {
return defaultRoles;
}
public void setDefaultRoles(String[] defaultRoles) {
this.defaultRoles = defaultRoles;
}
}

View file

@ -37,6 +37,6 @@
<module>api</module>
<module>picketlink</module>
<module>jpa</module>
<module>mongo</module>
<!-- <module>mongo</module> -->
</modules>
</project>

View file

@ -20,6 +20,7 @@
<dom4j.version>1.6.1</dom4j.version>
<mysql.version>5.1.25</mysql.version>
<slf4j.version>1.6.1</slf4j.version>
<jboss.version>7.1.1.Final</jboss.version>
</properties>
<url>http://keycloak.org</url>
@ -72,7 +73,8 @@
<module>admin-ui</module>
<module>examples</module>
<module>testsuite</module>
<!--<module>ui</module> -->
<module>server</module>
<module>dist</module>
</modules>
<dependencyManagement>

149
server/pom.xml Executable file
View file

@ -0,0 +1,149 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.0-alpha-1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server</artifactId>
<packaging>war</packaging>
<name>Keycloak Server</name>
<description/>
<dependencies>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>jose-jwt</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-ui</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-admin-ui-styles</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-picketlink</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-jpa</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-social-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-social-google</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-social-twitter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-social-facebook</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-forms</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-idm-api</artifactId>
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-idm-impl</artifactId>
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-idm-simple-schema</artifactId>
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
<artifactId>picketlink-config</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>jaxrs-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.3.161</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>auth-server</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,28 @@
package org.keycloak.server;
import org.jboss.resteasy.jwt.JsonSerialization;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.managers.ApplianceBootstrap;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.services.resources.KeycloakApplication;
import javax.servlet.ServletContext;
import javax.ws.rs.core.Context;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class KeycloakServerApplication extends KeycloakApplication {
public KeycloakServerApplication(@Context ServletContext servletContext) {
super(servletContext);
KeycloakSession session = factory.createSession();
session.getTransaction().begin();
ApplianceBootstrap bootstrap = new ApplianceBootstrap();
bootstrap.bootstrap(session);
session.getTransaction().commit();
}
}

View file

@ -0,0 +1,33 @@
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="keycloak-identity-store" transaction-type="RESOURCE_LOCAL">
<jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
<class>org.picketlink.idm.jpa.model.sample.simple.AttributedTypeEntity</class>
<class>org.picketlink.idm.jpa.model.sample.simple.AccountTypeEntity</class>
<class>org.picketlink.idm.jpa.model.sample.simple.RoleTypeEntity</class>
<class>org.picketlink.idm.jpa.model.sample.simple.GroupTypeEntity</class>
<class>org.picketlink.idm.jpa.model.sample.simple.IdentityTypeEntity</class>
<class>org.picketlink.idm.jpa.model.sample.simple.RelationshipTypeEntity</class>
<class>org.picketlink.idm.jpa.model.sample.simple.RelationshipIdentityTypeEntity</class>
<class>org.picketlink.idm.jpa.model.sample.simple.PartitionTypeEntity</class>
<class>org.picketlink.idm.jpa.model.sample.simple.PasswordCredentialTypeEntity</class>
<class>org.picketlink.idm.jpa.model.sample.simple.DigestCredentialTypeEntity</class>
<class>org.picketlink.idm.jpa.model.sample.simple.X509CredentialTypeEntity</class>
<class>org.picketlink.idm.jpa.model.sample.simple.OTPCredentialTypeEntity</class>
<class>org.picketlink.idm.jpa.model.sample.simple.AttributeTypeEntity</class>
<class>org.keycloak.models.picketlink.mappings.RealmEntity</class>
<class>org.keycloak.models.picketlink.mappings.ApplicationEntity</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<property name="hibernate.hbm2ddl.auto" value="create" />
<property name="hibernate.show_sql" value="false" />
<property name="hibernate.format_sql" value="false" />
</properties>
</persistence-unit>
</persistence>

View file

@ -0,0 +1,10 @@
<jboss-deployment-structure>
<deployment>
<!-- This allows you to define additional dependencies, it is the same as using the Dependencies: manifest attribute -->
<dependencies>
<module name="org.jboss.resteasy.jose-jwt"/>
<module name="org.jboss.resteasy.resteasy-crypto"/>
<module name="org.bouncycastle"/>
</dependencies>
</deployment>
</jboss-deployment-structure>

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<module-name>auth-server</module-name>
<servlet>
<servlet-name>Resteasy</servlet-name>
<servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServlet30Dispatcher</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>org.keycloak.server.KeycloakServerApplication</param-value>
</init-param>
<init-param>
<param-name>resteasy.servlet.mapping.prefix</param-name>
<param-value>/rest</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<filter>
<filter-name>Keycloak Session Management</filter-name>
<filter-class>org.keycloak.services.filters.KeycloakSessionServletFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>Keycloak Session Management</filter-name>
<url-pattern>/rest/*</url-pattern>
</filter-mapping>
<servlet-mapping>
<servlet-name>Resteasy</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
</web-app>

View file

@ -48,26 +48,53 @@ public class EmailSender {
private static final Logger log = Logger.getLogger(EmailSender.class);
private Properties properties;
private Map<String, String> config;
public EmailSender(Map<String, String> config) {
properties = new Properties();
for (Entry<String, String> e : config.entrySet()) {
properties.put("mail.smtp." + e.getKey(), e.getValue());
}
this.config = config;
}
public void send(String address, String subject, String body) throws MessagingException {
Session session = Session.getInstance(properties);
Properties props = new Properties();
props.setProperty("mail.smtp.host", config.get("host"));
boolean auth = "true".equals(config.get("auth"));
boolean ssl = "true".equals(config.get("ssl"));
boolean starttls = "true".equals(config.get("starttls"));
if (config.containsKey("port")) {
props.setProperty("mail.smtp.port", config.get("port"));
}
if (auth) {
props.put("mail.smtp.auth", "true");
}
if (ssl) {
props.put("mail.smtp.socketFactory.port", config.get("port"));
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
}
if (starttls) {
props.put("mail.smtp.starttls.enable", "true");
}
String from = config.get("from");
Session session = Session.getInstance(props);
Message msg = new MimeMessage(session);
msg.setFrom(new InternetAddress(properties.getProperty("mail.smtp.from")));
msg.setFrom(new InternetAddress(from));
msg.setSubject(subject);
msg.setText(body);
msg.saveChanges();
Transport transport = session.getTransport("smtp");
transport.connect(properties.getProperty("mail.smtp.user"), properties.getProperty("mail.smtp.password"));
if (auth) {
transport.connect(config.get("user"), config.get("password"));
} else {
transport.connect();
}
transport.sendMessage(msg, new InternetAddress[] { new InternetAddress(address) });
}

View file

@ -70,6 +70,11 @@ public class ApplicationManager {
if (roleRep.getDescription() != null) role.setDescription(roleRep.getDescription());
}
}
if (resourceRep.getDefaultRoles() != null) {
applicationModel.updateDefaultRoles(resourceRep.getDefaultRoles());
}
if (resourceRep.getRoleMappings() != null) {
for (UserRoleMappingRepresentation mapping : resourceRep.getRoleMappings()) {
UserModel user = realm.getUser(mapping.getUsername());
@ -114,6 +119,10 @@ public class ApplicationManager {
resource.setSurrogateAuthRequired(rep.isSurrogateAuthRequired());
resource.updateApplication();
if (rep.getDefaultRoles() != null) {
resource.updateDefaultRoles(rep.getDefaultRoles());
}
List<String> redirectUris = rep.getRedirectUris();
if (redirectUris != null) {
resource.getApplicationUser().setRedirectUris(new HashSet<String>(redirectUris));
@ -144,6 +153,10 @@ public class ApplicationManager {
rep.setWebOrigins(new LinkedList<String>(webOrigins));
}
if (!applicationModel.getDefaultRoles().isEmpty()) {
rep.setDefaultRoles(applicationModel.getDefaultRoles().toArray(new String[0]));
}
return rep;
}

View file

@ -56,24 +56,27 @@ public class AuthenticationManager {
String cookieName = KEYCLOAK_IDENTITY_COOKIE;
URI uri = RealmsResource.realmBaseUrl(uriInfo).build(realm.getId());
String cookiePath = uri.getPath();
return createLoginCookie(realm, user, cookieName, cookiePath);
return createLoginCookie(realm, user, null, cookieName, cookiePath);
}
public NewCookie createSaasIdentityCookie(RealmModel realm, UserModel user, UriInfo uriInfo) {
String cookieName = SaasService.SAAS_IDENTITY_COOKIE;
URI uri = SaasService.saasCookiePath(uriInfo).build();
String cookiePath = uri.getPath();
return createLoginCookie(realm, user, cookieName, cookiePath);
return createLoginCookie(realm, user, null, cookieName, cookiePath);
}
public NewCookie createAccountIdentityCookie(RealmModel realm, UserModel user, URI uri) {
public NewCookie createAccountIdentityCookie(RealmModel realm, UserModel user, UserModel client, URI uri) {
String cookieName = AccountService.ACCOUNT_IDENTITY_COOKIE;
String cookiePath = uri.getPath();
return createLoginCookie(realm, user, cookieName, cookiePath);
return createLoginCookie(realm, user, client, cookieName, cookiePath);
}
protected NewCookie createLoginCookie(RealmModel realm, UserModel user, String cookieName, String cookiePath) {
protected NewCookie createLoginCookie(RealmModel realm, UserModel user, UserModel client, String cookieName, String cookiePath) {
SkeletonKeyToken identityToken = createIdentityToken(realm, user.getLoginName());
if (client != null) {
identityToken.issuedFor(client.getLoginName());
}
String encoded = encodeToken(realm, identityToken);
boolean secureOnly = !realm.isSslNotRequired();
logger.debug("creatingLoginCookie - name: {0} path: {1}", cookieName, cookiePath);
@ -127,15 +130,17 @@ public class AuthenticationManager {
public UserModel authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
String cookieName = KEYCLOAK_IDENTITY_COOKIE;
return authenticateIdentityCookie(realm, uriInfo, headers, cookieName);
Auth auth = authenticateIdentityCookie(realm, uriInfo, headers, cookieName);
return auth != null ? auth.getUser() : null;
}
public UserModel authenticateSaasIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
String cookieName = SaasService.SAAS_IDENTITY_COOKIE;
return authenticateIdentityCookie(realm, uriInfo, headers, cookieName);
Auth auth = authenticateIdentityCookie(realm, uriInfo, headers, cookieName);
return auth != null ? auth.getUser() : null;
}
public UserModel authenticateAccountIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
public Auth authenticateAccountIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
String cookieName = AccountService.ACCOUNT_IDENTITY_COOKIE;
return authenticateIdentityCookie(realm, uriInfo, headers, cookieName);
}
@ -144,11 +149,19 @@ public class AuthenticationManager {
UserModel user = authenticateSaasIdentityCookie(realm, uriInfo, headers);
if (user != null) return user;
Auth auth = authenticateBearerToken(realm, headers);
return auth != null ? auth.getUser() : null;
}
public Auth authenticateAccountIdentity(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
Auth auth = authenticateAccountIdentityCookie(realm, uriInfo, headers);
if (auth != null) return auth;
return authenticateBearerToken(realm, headers);
}
protected UserModel authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers, String cookieName) {
protected Auth authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers, String cookieName) {
Cookie cookie = headers.getCookies().get(cookieName);
if (cookie == null) {
logger.debug("authenticateCookie could not find cookie: {0}", cookieName);
@ -163,13 +176,28 @@ public class AuthenticationManager {
expireIdentityCookie(realm, uriInfo);
return null;
}
Auth auth = new Auth(token);
UserModel user = realm.getUser(token.getPrincipal());
if (user == null || !user.isEnabled()) {
logger.debug("Unknown user in identity cookie");
expireIdentityCookie(realm, uriInfo);
return null;
}
return user;
auth.setUser(user);
if (token.getIssuedFor() != null) {
UserModel client = realm.getUser(token.getIssuedFor());
if (client == null || !client.isEnabled()) {
logger.debug("Unknown client in identity cookie");
expireIdentityCookie(realm, uriInfo);
return null;
}
auth.setClient(client);
}
return auth;
} catch (VerificationException e) {
logger.debug("Failed to verify identity cookie", e);
expireIdentityCookie(realm, uriInfo);
@ -177,11 +205,11 @@ public class AuthenticationManager {
return null;
}
public UserModel authenticateBearerToken(RealmModel realm, HttpHeaders headers) {
public Auth authenticateBearerToken(RealmModel realm, HttpHeaders headers) {
String tokenString = null;
String authHeader = headers.getHeaderString(HttpHeaders.AUTHORIZATION);
if (authHeader == null) {
throw new NotAuthorizedException("Bearer");
return null;
} else {
String[] split = authHeader.trim().split("\\s+");
if (split == null || split.length != 2) throw new NotAuthorizedException("Bearer");
@ -195,11 +223,24 @@ public class AuthenticationManager {
if (!token.isActive()) {
throw new NotAuthorizedException("token_expired");
}
Auth auth = new Auth(token);
UserModel user = realm.getUser(token.getPrincipal());
if (user == null || !user.isEnabled()) {
throw new NotAuthorizedException("invalid_user");
}
return user;
auth.setUser(user);
if (token.getIssuedFor() != null) {
UserModel client = realm.getUser(token.getIssuedFor());
if (client == null || !client.isEnabled()) {
throw new NotAuthorizedException("invalid_user");
}
auth.setClient(client);
}
return auth;
} catch (VerificationException e) {
logger.error("Failed to verify token", e);
throw new NotAuthorizedException("invalid_token");
@ -271,4 +312,34 @@ public class AuthenticationManager {
SUCCESS, ACCOUNT_DISABLED, ACTIONS_REQUIRED, INVALID_USER, INVALID_CREDENTIALS, MISSING_PASSWORD, MISSING_TOTP, FAILED
}
public static class Auth {
private SkeletonKeyToken token;
private UserModel user;
private UserModel client;
public Auth(SkeletonKeyToken token) {
this.token = token;
}
public SkeletonKeyToken getToken() {
return token;
}
public UserModel getUser() {
return user;
}
public UserModel getClient() {
return client;
}
void setUser(UserModel user) {
this.user = user;
}
void setClient(UserModel client) {
this.client = client;
}
}
}

View file

@ -110,7 +110,7 @@ public class RealmManager {
realm.updateRequiredApplicationCredentials(rep.getRequiredApplicationCredentials());
}
if (rep.getDefaultRoles() != null) {
realm.updateDefaultRoles(rep.getDefaultRoles());
realm.updateDefaultRoles(rep.getDefaultRoles().toArray(new String[rep.getDefaultRoles().size()]));
}
if (rep.getAccountManagement() != null && rep.getAccountManagement()) {
@ -129,9 +129,11 @@ public class RealmManager {
}
private void enableAccountManagement(RealmModel realm) {
ApplicationModel application = realm.getApplicationById(Constants.ACCOUNT_MANAGEMENT_APPLICATION);
ApplicationModel application = realm.getApplicationById(Constants.ACCOUNT_APPLICATION);
if (application == null) {
application = realm.addApplication(Constants.ACCOUNT_MANAGEMENT_APPLICATION);
application = realm.addApplication(Constants.ACCOUNT_APPLICATION);
application.addDefaultRole(Constants.ACCOUNT_PROFILE_ROLE);
application.addDefaultRole(Constants.ACCOUNT_MANAGE_ROLE);
UserCredentialModel password = new UserCredentialModel();
password.setType(UserCredentialModel.PASSWORD);
@ -146,7 +148,7 @@ public class RealmManager {
}
private void disableAccountManagement(RealmModel realm) {
ApplicationModel application = realm.getApplicationNameMap().get(Constants.ACCOUNT_MANAGEMENT_APPLICATION);
ApplicationModel application = realm.getApplicationNameMap().get(Constants.ACCOUNT_APPLICATION);
if (application != null) {
application.setEnabled(false); // TODO Should we delete the application instead?
}
@ -410,9 +412,11 @@ public class RealmManager {
rep.setLastName(user.getLastName());
rep.setFirstName(user.getFirstName());
rep.setEmail(user.getEmail());
if (user.getAttributes() != null && !user.getAttributes().isEmpty()) {
Map<String, String> attrs = new HashMap<String, String>();
attrs.putAll(user.getAttributes());
rep.setAttributes(attrs);
}
return rep;
}
@ -444,16 +448,14 @@ public class RealmManager {
rep.setSmtpServer(realm.getSmtpConfig());
rep.setSocialProviders(realm.getSocialConfig());
ApplicationModel accountManagementApplication = realm.getApplicationNameMap().get(Constants.ACCOUNT_MANAGEMENT_APPLICATION);
ApplicationModel accountManagementApplication = realm.getApplicationNameMap().get(Constants.ACCOUNT_APPLICATION);
rep.setAccountManagement(accountManagementApplication != null && accountManagementApplication.isEnabled());
List<RoleModel> defaultRoles = realm.getDefaultRoles();
if (defaultRoles.size() > 0) {
String[] d = new String[defaultRoles.size()];
for (int i = 0; i < d.length; i++) {
d[i] = defaultRoles.get(i).getName();
}
rep.setDefaultRoles(d);
List<String> defaultRoles = realm.getDefaultRoles();
if (!defaultRoles.isEmpty()) {
List<String> roleStrings = new ArrayList<String>();
roleStrings.addAll(defaultRoles);
rep.setDefaultRoles(roleStrings);
}
List<RequiredCredentialModel> requiredCredentialModels = realm.getRequiredCredentials();

View file

@ -19,7 +19,9 @@
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.keycloak.social;
package org.keycloak.services.managers;
import org.keycloak.social.RequestDetails;
import java.util.HashMap;
import java.util.Iterator;

View file

@ -15,6 +15,7 @@ import org.keycloak.representations.SkeletonKeyToken;
import javax.ws.rs.core.MultivaluedMap;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -51,12 +52,16 @@ public class TokenManager {
List<RoleModel> realmRolesRequested = code.getRealmRolesRequested();
MultivaluedMap<String, RoleModel> resourceRolesRequested = code.getResourceRolesRequested();
Set<String> realmMapping = realm.getRoleMappingValues(user);
realmMapping.addAll(realm.getDefaultRoles());
if (realmMapping != null && realmMapping.size() > 0 && (scopeMap == null || scopeMap.containsKey("realm"))) {
Set<String> scope = realm.getScopeMappingValues(client);
if (scope.size() > 0) {
Set<String> scopeRequest = null;
if (scopeMap != null) {
if (scopeRequest == null) {
scopeRequest = new HashSet<String>();
}
scopeRequest.addAll(scopeMap.get("realm"));
if (scopeRequest.contains(Constants.WILDCARD_ROLE)) scopeRequest = null;
}
@ -71,11 +76,15 @@ public class TokenManager {
}
for (ApplicationModel resource : realm.getApplications()) {
Set<String> mapping = resource.getRoleMappingValues(user);
mapping.addAll(resource.getDefaultRoles());
if (mapping != null && mapping.size() > 0 && (scopeMap == null || scopeMap.containsKey(resource.getName()))) {
Set<String> scope = resource.getScopeMappingValues(client);
if (scope.size() > 0) {
Set<String> scopeRequest = null;
if (scopeMap != null) {
if (scopeRequest == null) {
scopeRequest = new HashSet<String>();
}
scopeRequest.addAll(scopeMap.get(resource.getName()));
if (scopeRequest.contains(Constants.WILDCARD_ROLE)) scopeRequest = null;
}

View file

@ -27,15 +27,13 @@ import org.jboss.resteasy.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.AbstractOAuthClient;
import org.keycloak.jaxrs.JaxrsOAuthClient;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.Constants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.*;
import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.representations.SkeletonKeyToken;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.managers.AccessCodeEntry;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.TokenManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.flows.Flows;
@ -44,23 +42,11 @@ import org.keycloak.services.resources.flows.Pages;
import org.keycloak.services.resources.flows.Urls;
import org.keycloak.services.validation.Validation;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.Consumes;
import javax.ws.rs.ForbiddenException;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.ws.rs.ext.Providers;
import java.net.URI;
import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -98,18 +84,42 @@ public class AccountService {
}
private Response forwardToPage(String path, String template) {
UserModel user = getUser(false);
if (user != null) {
return Flows.forms(realm, request, uriInfo).setUser(user).forwardToForm(template);
AuthenticationManager.Auth auth = getAuth(false);
if (auth != null) {
if (!hasAccess(auth)) {
return noAccess();
}
return Flows.forms(realm, request, uriInfo).setUser(auth.getUser()).forwardToForm(template);
} else {
return login(path);
}
}
@Path("")
private Response noAccess() {
return Flows.forms(realm, request, uriInfo).setError("No access").forwardToErrorPage();
}
@Path("/")
@OPTIONS
public Response accountPreflight() {
return Cors.add(request, Response.ok()).auth().preflight().build();
}
@Path("/")
@GET
public Response accountPage() {
List<MediaType> types = headers.getAcceptableMediaTypes();
if (types.contains(MediaType.WILDCARD_TYPE) || (types.contains(MediaType.TEXT_HTML_TYPE))) {
return forwardToPage(null, Pages.ACCOUNT);
} else if (types.contains(MediaType.APPLICATION_JSON_TYPE)) {
AuthenticationManager.Auth auth = getAuth(true);
if (!hasAccess(auth, Constants.ACCOUNT_PROFILE_ROLE)) {
return Response.status(Response.Status.FORBIDDEN).build();
}
return Cors.add(request, Response.ok(RealmManager.toRepresentation(auth.getUser()))).auth().allowedOrigins(auth.getClient()).build();
} else {
return Response.notAcceptable(Variant.VariantListBuilder.newInstance().mediaTypes(MediaType.TEXT_HTML_TYPE, MediaType.APPLICATION_JSON_TYPE).build()).build();
}
}
@Path("social")
@ -136,12 +146,16 @@ public class AccountService {
return forwardToPage("access", Pages.ACCESS);
}
@Path("")
@Path("/")
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response processAccountUpdate(final MultivaluedMap<String, String> formData) {
AuthenticationManager.Auth auth = getAuth(true);
if (!hasAccess(auth)) {
return noAccess();
}
UserModel user = getUser(true);
UserModel user = auth.getUser();
String error = Validation.validateUpdateProfileForm(formData);
if (error != null) {
@ -159,7 +173,13 @@ public class AccountService {
@Path("totp-remove")
@GET
public Response processTotpRemove() {
UserModel user = getUser(true);
AuthenticationManager.Auth auth = getAuth(true);
if (!hasAccess(auth)) {
return noAccess();
}
UserModel user = auth.getUser();
user.setTotp(false);
return Flows.forms(realm, request, uriInfo).setError("successTotpRemoved").setErrorType(FormFlows.MessageType.SUCCESS)
.setUser(user).forwardToTotp();
@ -169,7 +189,12 @@ public class AccountService {
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response processTotpUpdate(final MultivaluedMap<String, String> formData) {
UserModel user = getUser(true);
AuthenticationManager.Auth auth = getAuth(true);
if (!hasAccess(auth)) {
return noAccess();
}
UserModel user = auth.getUser();
String totp = formData.getFirst("totp");
String totpSecret = formData.getFirst("totpSecret");
@ -196,7 +221,12 @@ public class AccountService {
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response processPasswordUpdate(final MultivaluedMap<String, String> formData) {
UserModel user = getUser(true);
AuthenticationManager.Auth auth = getAuth(true);
if (!hasAccess(auth)) {
return noAccess();
}
UserModel user = auth.getUser();
FormFlows forms = Flows.forms(realm, request, uriInfo).setUser(user);
@ -297,7 +327,7 @@ public class AccountService {
}
URI redirectUri = redirectBuilder.build(realm.getId());
NewCookie cookie = authManager.createAccountIdentityCookie(realm, accessCode.getUser(), Urls.accountBase(uriInfo.getBaseUri()).build(realm.getId()));
NewCookie cookie = authManager.createAccountIdentityCookie(realm, accessCode.getUser(), client, Urls.accountBase(uriInfo.getBaseUri()).build(realm.getId()));
return Response.status(302).cookie(cookie).location(redirectUri).build();
} finally {
authManager.expireCookie(AbstractOAuthClient.OAUTH_TOKEN_REQUEST_STATE, uriInfo.getAbsolutePath().getPath());
@ -319,7 +349,7 @@ public class AccountService {
String authUrl = Urls.realmLoginPage(uriInfo.getBaseUri(), realm.getId()).toString();
oauth.setAuthUrl(authUrl);
oauth.setClientId(Constants.ACCOUNT_MANAGEMENT_APPLICATION);
oauth.setClientId(Constants.ACCOUNT_APPLICATION);
URI accountUri = Urls.accountPageBuilder(uriInfo.getBaseUri()).path(AccountService.class, "loginRedirect").build(realm.getId());
@ -327,11 +357,42 @@ public class AccountService {
return oauth.redirect(uriInfo, accountUri.toString(), path);
}
private UserModel getUser(boolean required) {
UserModel user = authManager.authenticateAccountIdentityCookie(realm, uriInfo, headers);
if (user == null && required) {
private AuthenticationManager.Auth getAuth(boolean error) {
AuthenticationManager.Auth auth = authManager.authenticateAccountIdentity(realm, uriInfo, headers);
if (auth == null && error) {
throw new ForbiddenException();
}
return user;
return auth;
}
private boolean hasAccess(AuthenticationManager.Auth auth) {
return hasAccess(auth, null);
}
private boolean hasAccess(AuthenticationManager.Auth auth, String role) {
UserModel client = auth.getClient();
if (realm.hasRole(client, Constants.APPLICATION_ROLE)) {
// Tokens from cookies don't have roles
if (hasRole(client, Constants.ACCOUNT_MANAGE_ROLE) || (role != null && hasRole(client, role))) {
return true;
}
}
SkeletonKeyToken.Access access = auth.getToken().getResourceAccess(application.getName());
if (access != null) {
if (access.isUserInRole(Constants.ACCOUNT_MANAGE_ROLE) || (role != null && access.isUserInRole(role))) {
return true;
}
}
return false;
}
private boolean hasRole(UserModel user, String role) {
if (application.getDefaultRoles().contains(role)) {
return true;
}
return application.hasRole(user, role);
}
}

View file

@ -1,19 +1,37 @@
package org.keycloak.services.resources;
import org.jboss.resteasy.spi.HttpRequest;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import java.util.Set;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.models.UserModel;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class Cors {
public static final long DEFAULT_MAX_AGE = TimeUnit.HOURS.toSeconds(1);
public static final String DEFAULT_ALLOW_METHODS = "GET, OPTIONS";
public static final String ORIGIN = "Origin";
public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods";
public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials";
public static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age";
private HttpRequest request;
private ResponseBuilder response;
private Set<String> allowedOrigins;
private String[] allowedMethods;
private boolean preflight;
private boolean auth;
public Cors(HttpRequest request, ResponseBuilder response) {
this.request = request;
@ -24,18 +42,60 @@ public class Cors {
return new Cors(request, response);
}
public Cors allowedOrigins(Set<String> allowedOrigins) {
this.allowedOrigins = allowedOrigins;
public Cors preflight() {
preflight = true;
return this;
}
public Cors auth() {
auth = true;
return this;
}
public Cors allowedOrigins(UserModel client) {
if (client != null) {
allowedOrigins = client.getWebOrigins();
}
return this;
}
public Cors allowedMethods(String... allowedMethods) {
this.allowedMethods = allowedMethods;
return this;
}
public Response build() {
String origin = request.getHttpHeaders().getHeaderString("Origin");
if (origin == null || allowedOrigins == null || (!allowedOrigins.contains(origin))) {
String origin = request.getHttpHeaders().getHeaderString(ORIGIN);
if (origin == null) {
return response.build();
}
response.header("Access-Control-Allow-Origin", origin);
if (!preflight && (allowedOrigins == null || !allowedOrigins.contains(origin))) {
return response.build();
}
response.header(ACCESS_CONTROL_ALLOW_ORIGIN, origin);
if (allowedMethods != null) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < allowedMethods.length; i++) {
if (i > 0) {
sb.append(", ");
}
sb.append(allowedMethods[i]);
}
response.header(ACCESS_CONTROL_ALLOW_METHODS, sb.toString());
} else {
response.header(ACCESS_CONTROL_ALLOW_METHODS, DEFAULT_ALLOW_METHODS);
}
response.header(ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.toString(auth));
if (auth) {
response.header(ACCESS_CONTROL_ALLOW_HEADERS, "Authorization");
}
response.header(ACCESS_CONTROL_MAX_AGE, DEFAULT_MAX_AGE);
return response.build();
}

View file

@ -3,9 +3,9 @@ package org.keycloak.services.resources;
import org.keycloak.SkeletonKeyContextResolver;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.ModelProvider;
import org.keycloak.services.managers.SocialRequestManager;
import org.keycloak.services.managers.TokenManager;
import org.keycloak.services.utils.PropertiesManager;
import org.keycloak.social.SocialRequestManager;
import javax.annotation.PreDestroy;
import javax.servlet.ServletContext;

View file

@ -69,7 +69,7 @@ public class RealmsResource {
throw new NotFoundException();
}
ApplicationModel application = realm.getApplicationNameMap().get(Constants.ACCOUNT_MANAGEMENT_APPLICATION);
ApplicationModel application = realm.getApplicationNameMap().get(Constants.ACCOUNT_APPLICATION);
if (application == null || !application.isEnabled()) {
logger.debug("account management not enabled");
throw new NotFoundException();

View file

@ -39,12 +39,11 @@ import org.keycloak.services.resources.flows.Urls;
import org.keycloak.social.AuthCallback;
import org.keycloak.social.AuthRequest;
import org.keycloak.social.RequestDetails;
import org.keycloak.social.RequestDetailsBuilder;
import org.keycloak.social.SocialConstants;
import org.keycloak.social.SocialProvider;
import org.keycloak.social.SocialProviderConfig;
import org.keycloak.social.SocialProviderException;
import org.keycloak.social.SocialRequestManager;
import org.keycloak.services.managers.SocialRequestManager;
import org.keycloak.social.SocialUser;
import javax.imageio.spi.ServiceRegistry;
@ -181,19 +180,12 @@ public class SocialResource {
}
realm.addSocialLink(user, socialLink);
for (RoleModel role : realm.getDefaultRoles()) {
realm.grantRole(user, role);
}
} else {
// Redirect user to registration screen with prefilled data from social provider
MultivaluedMap<String, String> formData = fillRegistrationFormWithSocialData(socialUser);
RequestDetailsBuilder reqDetailsBuilder = RequestDetailsBuilder.createFromRequestDetails(requestData);
reqDetailsBuilder.putSocialAttribute(SocialConstants.ATTR_SOCIAL_LINK, socialLink);
String requestId = UUID.randomUUID().toString();
socialRequestManager.addRequest(requestId, reqDetailsBuilder.build());
socialRequestManager.addRequest(requestId, RequestDetails.create(requestData).build());
boolean secureOnly = !realm.isSslNotRequired();
String cookiePath = Urls.socialBase(uriInfo.getBaseUri()).build().getPath();
logger.debug("creating cookie for social registration - name: {0} path: {1}", SocialConstants.SOCIAL_REGISTRATION_COOKIE,
@ -241,7 +233,7 @@ public class SocialResource {
try {
AuthRequest authRequest = provider.getAuthUrl(config);
RequestDetails socialRequest = RequestDetailsBuilder.create(providerId)
RequestDetails socialRequest = RequestDetails.create(providerId)
.putSocialAttributes(authRequest.getAttributes()).putClientAttribute("realmId", realmId)
.putClientAttribute("clientId", clientId).putClientAttribute("scope", scope)
.putClientAttribute("state", state).putClientAttribute("redirectUri", redirectUri).build();
@ -285,7 +277,6 @@ public class SocialResource {
String scope = requestData.getClientAttribute("scope");
String state = requestData.getClientAttribute("state");
String redirectUri = requestData.getClientAttribute("redirectUri");
SocialLinkModel socialLink = (SocialLinkModel)requestData.getSocialAttribute(SocialConstants.ATTR_SOCIAL_LINK);
Response response1 = tokenService.processRegisterImpl(clientId, scope, state, redirectUri, formData, true);
@ -301,7 +292,7 @@ public class SocialResource {
// Normally shouldn't happen
throw new IllegalStateException("User " + username + " not found in the realm");
}
realm.addSocialLink(user, socialLink);
realm.addSocialLink(user, new SocialLinkModel(requestData.getProviderId(), username));
// Expire cookie and invalidate requestData
String cookiePath = Urls.socialBase(uriInfo.getBaseUri()).build().getPath();

View file

@ -323,10 +323,6 @@ public class TokenService {
realm.updateCredential(user, credentials);
}
for (RoleModel role : realm.getDefaultRoles()) {
realm.grantRole(user, role);
}
return null;
}
@ -427,7 +423,7 @@ public class TokenService {
logger.debug("accessRequest SUCCESS");
AccessTokenResponse res = accessTokenResponse(realm.getPrivateKey(), accessCode.getToken());
return Cors.add(request, Response.ok(res)).allowedOrigins(client.getWebOrigins()).build();
return Cors.add(request, Response.ok(res)).allowedOrigins(client).build();
}
protected AccessTokenResponse accessTokenResponse(PrivateKey privateKey, SkeletonKeyToken token) {
@ -468,7 +464,7 @@ public class TokenService {
}
UserModel client = realm.getUser(clientId);
if (client == null) {
logger.warn("Unknown login requester.");
logger.warn("Unknown login requester: " + clientId);
oauth.forwardToSecurityFailure("Unknown login requester.");
transaction.rollback();
return null;

View file

@ -55,7 +55,7 @@ public class EmailSenderTest {
}
@Test
public void sendMail() throws AddressException, MessagingException, IOException {
public void sendMail() throws MessagingException, IOException {
emailSender.send("test@test.com", "Test subject", "Test body");
MimeMessage[] receivedMessages = greenMail.getReceivedMessages();

View file

@ -77,7 +77,7 @@ public class AdapterTest extends AbstractKeycloakTest {
Assert.assertEquals(realmModel.getPublicKeyPem(), "0234234");
Assert.assertEquals(realmModel.isAutomaticRegistrationAfterSocialLogin(), true);
Assert.assertEquals(1, realmModel.getDefaultRoles().size());
Assert.assertEquals("foo", realmModel.getDefaultRoles().get(0).getName());
Assert.assertEquals("foo", realmModel.getDefaultRoles().get(0));
}
@Test
@ -106,7 +106,7 @@ public class AdapterTest extends AbstractKeycloakTest {
Assert.assertEquals(realmModel.getPublicKeyPem(), "0234234");
Assert.assertEquals(realmModel.isAutomaticRegistrationAfterSocialLogin(), true);
Assert.assertEquals(1, realmModel.getDefaultRoles().size());
Assert.assertEquals("foo", realmModel.getDefaultRoles().get(0).getName());
Assert.assertEquals("foo", realmModel.getDefaultRoles().get(0));
String id = realmModel.getId();
System.out.println("id: " + id);

View file

@ -45,6 +45,8 @@ public class ApplicationModelTest extends AbstractKeycloakServerTest {
application.setName("app-name");
application.addRole("role-1");
application.addRole("role-2");
application.addDefaultRole("role-1");
application.addDefaultRole("role-2");
application.getApplicationUser().addRedirectUri("redirect-1");
application.getApplicationUser().addRedirectUri("redirect-2");
@ -83,6 +85,7 @@ public class ApplicationModelTest extends AbstractKeycloakServerTest {
Assert.assertEquals(expected.getName(), actual.getName());
Assert.assertEquals(expected.getBaseUrl(), actual.getBaseUrl());
Assert.assertEquals(expected.getManagementUrl(), actual.getManagementUrl());
Assert.assertEquals(expected.getDefaultRoles(), actual.getDefaultRoles());
UserModel auser = actual.getApplicationUser();
UserModel euser = expected.getApplicationUser();

View file

@ -85,7 +85,7 @@ public class ModelTest extends AbstractKeycloakServerTest {
Assert.assertEquals(expected.getPublicKeyPem(), actual.getPublicKeyPem());
Assert.assertEquals(expected.getPrivateKeyPem(), actual.getPrivateKeyPem());
assertEquals(expected.getDefaultRoles(), actual.getDefaultRoles());
Assert.assertEquals(expected.getDefaultRoles(), actual.getDefaultRoles());
Assert.assertEquals(expected.getSmtpConfig(), actual.getSmtpConfig());
Assert.assertEquals(expected.getSocialConfig(), actual.getSocialConfig());

View file

@ -28,11 +28,11 @@ import java.util.Map;
*/
public class AuthCallback {
private Map<String, Object> attributes;
private Map<String, String> attributes;
private Map<String, String[]> queryParams;
public AuthCallback(Map<String, Object> attributes, Map<String, String[]> queryParams) {
public AuthCallback(Map<String, String> attributes, Map<String, String[]> queryParams) {
this.attributes = attributes;
this.queryParams = queryParams;
}

View file

@ -21,7 +21,9 @@
*/
package org.keycloak.social;
import javax.ws.rs.core.UriBuilder;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
/**
@ -33,9 +35,17 @@ public class AuthRequest {
private URI authUri;
private Map<String, Object> attributes;
private Map<String, String> attributes;
AuthRequest(String id, URI authUri, Map<String, Object> attributes) {
public static AuthRequestBuilder create(String id, String path) {
AuthRequestBuilder req = new AuthRequestBuilder();
req.id = id;
req.b = UriBuilder.fromUri(path);
req.attributes = new HashMap<String, String>();
return req;
}
private AuthRequest(String id, URI authUri, Map<String, String> attributes) {
this.id = id;
this.authUri = authUri;
this.attributes = attributes;
@ -49,8 +59,35 @@ public class AuthRequest {
return authUri;
}
public Map<String, Object> getAttributes() {
public Map<String, String> getAttributes() {
return attributes;
}
public static class AuthRequestBuilder {
private UriBuilder b;
private Map<String, String> attributes;
private String id;
private AuthRequestBuilder() {
}
public AuthRequestBuilder setQueryParam(String name, String value) {
b.queryParam(name, value);
return this;
}
public AuthRequestBuilder setAttribute(String name, String value) {
attributes.put(name, value);
return this;
}
public AuthRequest build() {
return new AuthRequest(id, b.build(), attributes);
}
}
}

View file

@ -1,64 +0,0 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2012, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.keycloak.social;
import javax.ws.rs.core.UriBuilder;
import java.util.HashMap;
import java.util.Map;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class AuthRequestBuilder {
private UriBuilder b;
private Map<String, Object> attributes;
private String id;
private AuthRequestBuilder() {
}
public static AuthRequestBuilder create(String id, String path) {
AuthRequestBuilder req = new AuthRequestBuilder();
req.id = id;
req.b = UriBuilder.fromUri(path);
req.attributes = new HashMap<String, Object>();
return req;
}
public AuthRequestBuilder setQueryParam(String name, String value) {
b.queryParam(name, value);
return this;
}
public AuthRequestBuilder setAttribute(String name, Object value) {
attributes.put(name, value);
return this;
}
public AuthRequest build() {
return new AuthRequest(id, b.build(), attributes);
}
}

View file

@ -21,6 +21,7 @@
*/
package org.keycloak.social;
import java.util.HashMap;
import java.util.Map;
/**
@ -32,9 +33,27 @@ public class RequestDetails {
private Map<String, String> clientAttributes;
private Map<String, Object> socialAttributes;
private Map<String, String> socialAttributes;
RequestDetails(String providerId, Map<String, String> clientAttributes, Map<String, Object> socialAttributes) {
public static RequestDetailsBuilder create(String providerId) {
RequestDetailsBuilder req = new RequestDetailsBuilder();
req.providerId = providerId;
req.clientAttributes = new HashMap<String, String>();
req.socialAttributes = new HashMap<String, String>();
return req;
}
public static RequestDetailsBuilder create(RequestDetails from) {
RequestDetailsBuilder req = new RequestDetailsBuilder();
req.providerId = from.getProviderId();
req.clientAttributes = new HashMap<String, String>();
req.clientAttributes.putAll(from.getClientAttributes());
req.socialAttributes = new HashMap<String, String>();
req.socialAttributes.putAll(from.getSocialAttributes());
return req;
}
private RequestDetails(String providerId, Map<String, String> clientAttributes, Map<String, String> socialAttributes) {
this.providerId = providerId;
this.clientAttributes = clientAttributes;
this.socialAttributes = socialAttributes;
@ -52,12 +71,50 @@ public class RequestDetails {
return clientAttributes;
}
public Object getSocialAttribute(String name) {
public String getSocialAttribute(String name) {
return socialAttributes.get(name);
}
public Map<String, Object> getSocialAttributes() {
public Map<String, String> getSocialAttributes() {
return socialAttributes;
}
public static class RequestDetailsBuilder {
private String providerId;
private Map<String, String> clientAttributes;
private Map<String, String> socialAttributes;
private RequestDetailsBuilder() {
}
public RequestDetailsBuilder putClientAttribute(String name, String value) {
clientAttributes.put(name, value);
return this;
}
public RequestDetailsBuilder putClientAttributes(Map<String, String> attributes) {
clientAttributes.putAll(attributes);
return this;
}
public RequestDetailsBuilder putSocialAttribute(String name, String value) {
socialAttributes.put(name, value);
return this;
}
public RequestDetailsBuilder putSocialAttributes(Map<String, String> attributes) {
socialAttributes.putAll(attributes);
return this;
}
public RequestDetails build() {
return new RequestDetails(providerId, clientAttributes, socialAttributes);
}
}
}

View file

@ -1,83 +0,0 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2012, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.keycloak.social;
import java.util.HashMap;
import java.util.Map;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class RequestDetailsBuilder {
private String providerId;
private Map<String, String> clientAttributes;
private Map<String, Object> socialAttributes;
private RequestDetailsBuilder() {
}
public static RequestDetailsBuilder create(String providerId) {
RequestDetailsBuilder req = new RequestDetailsBuilder();
req.providerId = providerId;
req.clientAttributes = new HashMap<String, String>();
req.socialAttributes = new HashMap<String, Object>();
return req;
}
public static RequestDetailsBuilder createFromRequestDetails(RequestDetails from) {
RequestDetailsBuilder req = new RequestDetailsBuilder();
req.providerId = from.getProviderId();
req.clientAttributes = new HashMap<String, String>();
req.clientAttributes.putAll(from.getClientAttributes());
req.socialAttributes = new HashMap<String, Object>();
req.socialAttributes.putAll(from.getSocialAttributes());
return req;
}
public RequestDetailsBuilder putClientAttribute(String name, String value) {
clientAttributes.put(name, value);
return this;
}
public RequestDetailsBuilder putClientAttributes(Map<String, String> attributes) {
clientAttributes.putAll(attributes);
return this;
}
public RequestDetailsBuilder putSocialAttribute(String name, Object value) {
socialAttributes.put(name, value);
return this;
}
public RequestDetailsBuilder putSocialAttributes(Map<String, Object> attributes) {
socialAttributes.putAll(attributes);
return this;
}
public RequestDetails build() {
return new RequestDetails(providerId, clientAttributes, socialAttributes);
}
}

View file

@ -50,8 +50,4 @@ public class SocialProviderConfig {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
}

View file

@ -4,7 +4,6 @@ import org.jboss.resteasy.client.jaxrs.ResteasyClient;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import org.keycloak.social.AuthCallback;
import org.keycloak.social.AuthRequest;
import org.keycloak.social.AuthRequestBuilder;
import org.keycloak.social.SocialProvider;
import org.keycloak.social.SocialProviderConfig;
import org.keycloak.social.SocialProviderException;
@ -45,13 +44,9 @@ public class FacebookProvider implements SocialProvider {
public AuthRequest getAuthUrl(SocialProviderConfig config) throws SocialProviderException {
String state = UUID.randomUUID().toString();
AuthRequestBuilder b = AuthRequestBuilder.create(state, AUTHENTICATION_ENDPOINT_URL).setQueryParam("client_id", config.getKey())
return AuthRequest.create(state, AUTHENTICATION_ENDPOINT_URL).setQueryParam("client_id", config.getKey())
.setQueryParam("response_type", DEFAULT_RESPONSE_TYPE).setQueryParam("scope", DEFAULT_SCOPE)
.setQueryParam("redirect_uri", config.getCallbackUrl()).setQueryParam("state", state);
b.setAttribute("state", state);
return b.build();
.setQueryParam("redirect_uri", config.getCallbackUrl()).setQueryParam("state", state).setAttribute("state", state).build();
}
@Override

View file

@ -21,6 +21,15 @@
*/
package org.keycloak.social.google;
import java.util.UUID;
import org.keycloak.social.AuthCallback;
import org.keycloak.social.AuthRequest;
import org.keycloak.social.SocialProvider;
import org.keycloak.social.SocialProviderConfig;
import org.keycloak.social.SocialProviderException;
import org.keycloak.social.SocialUser;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeTokenRequest;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
@ -29,15 +38,6 @@ import com.google.api.client.json.jackson.JacksonFactory;
import com.google.api.services.oauth2.Oauth2;
import com.google.api.services.oauth2.model.Tokeninfo;
import com.google.api.services.oauth2.model.Userinfo;
import org.keycloak.social.AuthCallback;
import org.keycloak.social.AuthRequest;
import org.keycloak.social.AuthRequestBuilder;
import org.keycloak.social.SocialProvider;
import org.keycloak.social.SocialProviderConfig;
import org.keycloak.social.SocialProviderException;
import org.keycloak.social.SocialUser;
import java.util.UUID;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -63,13 +63,9 @@ public class GoogleProvider implements SocialProvider {
public AuthRequest getAuthUrl(SocialProviderConfig config) throws SocialProviderException {
String state = UUID.randomUUID().toString();
AuthRequestBuilder b = AuthRequestBuilder.create(state, AUTH_PATH).setQueryParam("client_id", config.getKey())
return AuthRequest.create(state, AUTH_PATH).setQueryParam("client_id", config.getKey())
.setQueryParam("response_type", DEFAULT_RESPONSE_TYPE).setQueryParam("scope", DEFAULT_SCOPE)
.setQueryParam("redirect_uri", config.getCallbackUrl()).setQueryParam("state", state);
b.setAttribute("state", state);
return b.build();
.setQueryParam("redirect_uri", config.getCallbackUrl()).setQueryParam("state", state).setAttribute("state", state).build();
}
@Override

View file

@ -23,7 +23,6 @@ package org.keycloak.social.twitter;
import org.keycloak.social.AuthCallback;
import org.keycloak.social.AuthRequest;
import org.keycloak.social.AuthRequestBuilder;
import org.keycloak.social.SocialProvider;
import org.keycloak.social.SocialProviderConfig;
import org.keycloak.social.SocialProviderException;
@ -50,7 +49,7 @@ public class TwitterProvider implements SocialProvider {
RequestToken requestToken = twitter.getOAuthRequestToken(request.getCallbackUrl());
return AuthRequestBuilder.create(requestToken.getToken(), requestToken.getAuthenticationURL())
return AuthRequest.create(requestToken.getToken(), requestToken.getAuthenticationURL())
.setAttribute("token", requestToken.getToken()).setAttribute("tokenSecret", requestToken.getTokenSecret())
.build();
} catch (Exception e) {

View file

@ -191,6 +191,10 @@
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-chrome-driver</artifactId>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>

View file

@ -1,5 +1,7 @@
log4j.rootLogger=debug, stdout
log4j.rootLogger=info, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] %m%n
log4j.logger.org.keycloak=debug

View file

@ -2,7 +2,6 @@ package org.keycloak.testsuite;
import org.keycloak.social.AuthCallback;
import org.keycloak.social.AuthRequest;
import org.keycloak.social.AuthRequestBuilder;
import org.keycloak.social.SocialProvider;
import org.keycloak.social.SocialProviderConfig;
import org.keycloak.social.SocialProviderException;
@ -23,12 +22,8 @@ public class DummySocial implements SocialProvider {
public AuthRequest getAuthUrl(SocialProviderConfig config) throws SocialProviderException {
String state = UUID.randomUUID().toString();
AuthRequestBuilder b = AuthRequestBuilder.create(state, AUTH_PATH).setQueryParam("response_type", "token")
.setQueryParam("redirect_uri", config.getCallbackUrl()).setQueryParam("state", state);
b.setAttribute("state", state);
return b.build();
return AuthRequest.create(state, AUTH_PATH).setQueryParam("response_type", "token")
.setQueryParam("redirect_uri", config.getCallbackUrl()).setQueryParam("state", state).setAttribute("state", state).build();
}
@Override

View file

@ -30,10 +30,14 @@ import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.jboss.resteasy.jose.Base64Url;
import org.jboss.resteasy.jwt.JsonSerialization;
import org.jboss.resteasy.security.PemUtils;
import org.json.JSONObject;
import org.junit.Assert;
import org.keycloak.RSATokenVerifier;
import org.keycloak.VerificationException;
import org.keycloak.representations.SkeletonKeyScope;
import org.keycloak.representations.SkeletonKeyToken;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
@ -67,17 +71,21 @@ public class OAuthClient {
private String redirectUri = "http://localhost:8081/app/auth";
private String scope;
private SkeletonKeyScope scope;
private String state;
private PublicKey realmPublicKey;
public OAuthClient(WebDriver driver) throws Exception {
public OAuthClient(WebDriver driver) {
this.driver = driver;
try {
JSONObject realmJson = new JSONObject(IOUtils.toString(getClass().getResourceAsStream("/testrealm.json")));
realmPublicKey = PemUtils.decodePublicKey(realmJson.getString("publicKey"));
} catch (Exception e) {
throw new RuntimeException("Failed to retrieve realm public key", e);
}
}
public AuthorizationCodeResponse doLogin(String username, String password) {
@ -90,7 +98,15 @@ public class OAuthClient {
return new AuthorizationCodeResponse(this);
}
public AccessTokenResponse doAccessTokenRequest(String code, String password) throws Exception {
public void doLoginGrant(String username, String password) {
openLoginForm();
driver.findElement(By.id("username")).sendKeys(username);
driver.findElement(By.id("password")).sendKeys(password);
driver.findElement(By.cssSelector("input[type=\"submit\"]")).click();
}
public AccessTokenResponse doAccessTokenRequest(String code, String password) {
HttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost(getAccessTokenUrl());
@ -114,27 +130,19 @@ public class OAuthClient {
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(parameters, Charset.forName("UTF-8"));
post.setEntity(formEntity);
try {
return new AccessTokenResponse(client.execute(post));
} catch (Exception e) {
throw new RuntimeException("Failed to retrieve access token", e);
}
}
public SkeletonKeyToken verifyToken(String token) throws Exception {
public SkeletonKeyToken verifyToken(String token) {
try {
return RSATokenVerifier.verifyToken(token, realmPublicKey, realm);
} catch (VerificationException e) {
throw new RuntimeException("Failed to verify token", e);
}
public boolean isAuthorizationResponse() {
return getCurrentRequest().equals(redirectUri) && getCurrentQuery().containsKey("code");
}
public String getState() {
return state;
}
public String getClientId() {
return clientId;
}
public String getResponseType() {
return responseType;
}
public String getCurrentRequest() {
@ -174,10 +182,6 @@ public class OAuthClient {
return redirectUri;
}
public String getScope() {
return scope;
}
public String getLoginFormUrl() {
UriBuilder b = UriBuilder.fromUri(baseUrl + "/realms/" + realm + "/tokens/login");
if (responseType != null) {
@ -190,7 +194,12 @@ public class OAuthClient {
b.queryParam("redirect_uri", redirectUri);
}
if (scope != null) {
b.queryParam("scope", scope);
try {
b.queryParam("scope", Base64Url.encode(JsonSerialization.toByteArray(scope, false)));
} catch (Exception e) {
throw new RuntimeException("Failed to serialize scope", e);
}
}
if (state != null) {
b.queryParam("state", state);
@ -223,8 +232,11 @@ public class OAuthClient {
return this;
}
public OAuthClient scope(String scope) {
this.scope = scope;
public OAuthClient addScope(String resource, String... roles) {
if (scope == null) {
scope = new SkeletonKeyScope();
}
scope.addAll(resource, roles);
return this;
}

View file

@ -1,111 +0,0 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2012, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.keycloak.testsuite;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import org.keycloak.PemUtils;
import org.keycloak.RSATokenVerifier;
import org.keycloak.representations.SkeletonKeyToken;
import org.keycloak.servlet.ServletOAuthClient;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.UriBuilder;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.PublicKey;
import java.util.Map;
/**
* @author <a href="mailto:vrockai@redhat.com">Viliam Rockai</a>
*/
public class OAuthGrantServlet extends HttpServlet {
public static ServletOAuthClient client;
private static String baseUrl = Constants.AUTH_SERVER_ROOT + "/rest";
private static String realm = "test";
private static String realmKeyPem = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvg" +
"cwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/" +
"p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB";
public void init() {
client = new ServletOAuthClient();
client.setClientId("third-party");
client.setPassword("password");
client.setAuthUrl(UriBuilder.fromUri(baseUrl + "/realms/" + realm + "/tokens/login").build().toString());
client.setCodeUrl(UriBuilder.fromUri(baseUrl + "/realms/" + realm + "/tokens/access/codes").build().toString());
client.setClient(new ResteasyClientBuilder().build());
client.start();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
PrintWriter pw = resp.getWriter();
// Error "access_denied" happens after clicking on cancel when asked for granting permission
if (req.getParameterValues("error") != null){
pw.print("<html><head><title></title></head><body>Access rights not granted.</body></html>");
// Code is sent as a parameter in case that access was granted
} else if(req.getParameterValues("code") != null) {
String token = client.getBearerToken(req);
pw.print("<html><head><title></title></head><body>Access rights granted.<br/>Token:"+token+"<br/>");
// Check whether the token itself is relevant
try {
PublicKey realmKey = PemUtils.decodePublicKey(realmKeyPem);
SkeletonKeyToken skeletonToken = RSATokenVerifier.verifyToken(token, realmKey, realm);
// Writing app/role information on a page in format which is easy to assert in a test.
pw.print("Role:");
for(String role: skeletonToken.getRealmAccess().getRoles()){
pw.print(role);
}
pw.print(".<br/>");
for(Map.Entry<String, SkeletonKeyToken.Access> entry: skeletonToken.getResourceAccess().entrySet()){
pw.print("App:"+entry.getKey()+";");
for(String role: entry.getValue().getRoles()){
pw.print(role);
}
}
pw.print(".<br/>");
} catch (Exception e){
}
pw.print("</body></html>");
// If no code was sent or error happened, it's 1st visit to servlet and we need to ask for permissions
} else {
client.redirectRelative("", req, resp);
}
pw.flush();
}
}

View file

@ -0,0 +1,245 @@
package org.keycloak.testsuite.account;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONObject;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.Constants;
import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.OAuthGrantPage;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriBuilder;
import java.io.IOException;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ProfileTest {
@ClassRule
public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
UserModel user = appRealm.getUser("test-user@localhost");
user.setFirstName("First");
user.setLastName("Last");
user.setAttribute("key1", "value1");
user.setAttribute("key2", "value2");
ApplicationModel accountApp = appRealm.getApplicationNameMap().get(org.keycloak.models.Constants.ACCOUNT_APPLICATION);
ApplicationModel app = appRealm.getApplicationNameMap().get("test-app");
accountApp.addScopeMapping(app.getApplicationUser(), org.keycloak.models.Constants.ACCOUNT_PROFILE_ROLE);
app.getApplicationUser().addWebOrigin("http://localtest.me:8081");
UserModel thirdParty = appRealm.getUser("third-party");
accountApp.addScopeMapping(thirdParty, org.keycloak.models.Constants.ACCOUNT_PROFILE_ROLE);
}
});
@Rule
public WebRule webRule = new WebRule(this);
@WebResource
protected WebDriver driver;
@WebResource
protected OAuthClient oauth;
@WebResource
protected AccountUpdateProfilePage profilePage;
@WebResource
protected LoginPage loginPage;
@WebResource
protected OAuthGrantPage grantPage;
private List<String> defaultRoles;
@Test
public void getProfile() throws Exception {
oauth.doLogin("test-user@localhost", "password");
String code = oauth.getCurrentQuery().get("code");
String token = oauth.doAccessTokenRequest(code, "password").getAccessToken();
HttpResponse response = doGetProfile(token, null);
assertEquals(200, response.getStatusLine().getStatusCode());
JSONObject profile = new JSONObject(IOUtils.toString(response.getEntity().getContent()));
assertEquals("test-user@localhost", profile.getString("username"));
assertEquals("test-user@localhost", profile.getString("email"));
assertEquals("First", profile.getString("firstName"));
assertEquals("Last", profile.getString("lastName"));
JSONObject attributes = profile.getJSONObject("attributes");
assertEquals(2, attributes.length());
assertEquals("value1", attributes.getString("key1"));
assertEquals("value2", attributes.getString("key2"));
}
@Test
public void getProfileCors() throws Exception {
oauth.doLogin("test-user@localhost", "password");
String code = oauth.getCurrentQuery().get("code");
String token = oauth.doAccessTokenRequest(code, "password").getAccessToken();
driver.navigate().to("http://localtest.me:8081/app");
String[] response = doGetProfileJs(token);
assertEquals("200", response[0]);
}
@Test
public void getProfileCorsInvalidOrigin() throws Exception {
oauth.doLogin("test-user@localhost", "password");
String code = oauth.getCurrentQuery().get("code");
String token = oauth.doAccessTokenRequest(code, "password").getAccessToken();
driver.navigate().to("http://invalid.localtest.me:8081");
try {
doGetProfileJs(token);
fail("Expected failure");
} catch (Throwable t) {
}
}
@Test
public void getProfileCookieAuth() throws Exception {
profilePage.open();
loginPage.login("test-user@localhost", "password");
String[] response = doGetProfileJs(null);
assertEquals("200", response[0]);
JSONObject profile = new JSONObject(response[1]);
assertEquals("test-user@localhost", profile.getString("username"));
}
@Test
public void getProfileNoAuth() throws Exception {
HttpResponse response = doGetProfile(null, null);
assertEquals(403, response.getStatusLine().getStatusCode());
}
@Test
public void getProfileNoAccess() throws Exception {
try {
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
ApplicationModel app = appRealm.getApplicationNameMap().get(org.keycloak.models.Constants.ACCOUNT_APPLICATION);
defaultRoles = app.getDefaultRoles();
app.updateDefaultRoles(new String[0]);
}
});
oauth.doLogin("test-user@localhost", "password");
String code = oauth.getCurrentQuery().get("code");
String token = oauth.doAccessTokenRequest(code, "password").getAccessToken();
HttpResponse response = doGetProfile(token, null);
assertEquals(403, response.getStatusLine().getStatusCode());
} finally {
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
appRealm.getApplicationNameMap().get(org.keycloak.models.Constants.ACCOUNT_APPLICATION).updateDefaultRoles((String[]) defaultRoles.toArray(new String[0]));
}
});
}
}
@Test
public void getProfileOAuthClient() throws Exception {
oauth.addScope(org.keycloak.models.Constants.ACCOUNT_APPLICATION, org.keycloak.models.Constants.ACCOUNT_PROFILE_ROLE);
oauth.clientId("third-party");
oauth.doLoginGrant("test-user@localhost", "password");
grantPage.accept();
String token = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get("code"), "password").getAccessToken();
HttpResponse response = doGetProfile(token, null);
assertEquals(200, response.getStatusLine().getStatusCode());
JSONObject profile = new JSONObject(IOUtils.toString(response.getEntity().getContent()));
assertEquals("test-user@localhost", profile.getString("username"));
}
@Test
public void getProfileOAuthClientNoScope() throws Exception {
oauth.addScope(org.keycloak.models.Constants.ACCOUNT_APPLICATION);
oauth.clientId("third-party");
oauth.doLoginGrant("test-user@localhost", "password");
String token = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get("code"), "password").getAccessToken();
HttpResponse response = doGetProfile(token, null);
assertEquals(403, response.getStatusLine().getStatusCode());
}
private URI getAccountURI() {
return UriBuilder.fromUri(Constants.AUTH_SERVER_ROOT + "/rest/realms/" + oauth.getRealm() + "/account").build();
}
private HttpResponse doGetProfile(String token, String origin) throws IOException {
HttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet(UriBuilder.fromUri(getAccountURI()).build());
if (token != null) {
get.setHeader(HttpHeaders.AUTHORIZATION, "bearer " + token);
}
if (origin != null) {
get.setHeader("Origin", origin);
}
get.setHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON);
return client.execute(get);
}
private String[] doGetProfileJs(String token) {
StringBuilder sb = new StringBuilder();
sb.append("var req = new XMLHttpRequest();\n");
sb.append("req.open('GET', '" + getAccountURI().toString() + "', false);\n");
if (token != null) {
sb.append("req.setRequestHeader('Authorization', 'Bearer " + token + "');\n");
}
sb.append("req.setRequestHeader('Accept', 'application/json');\n");
sb.append("req.send(null);\n");
sb.append("return req.status + '///' + req.responseText;\n");
JavascriptExecutor js = (JavascriptExecutor) driver;
String response = (String) js.executeScript(sb.toString());
return response.split("///");
}
}

View file

@ -76,7 +76,7 @@ public class RequiredActionMultipleActionsTest {
protected LoginUpdateProfilePage updateProfilePage;
@Test
public void updateProfileAndPassword() {
public void updateProfileAndPassword() throws Exception {
loginPage.open();
loginPage.login("test-user@localhost", "password");

View file

@ -81,7 +81,7 @@ public class RequiredActionResetPasswordTest {
protected LoginPasswordUpdatePage changePasswordPage;
@Test
public void tempPassword() {
public void tempPassword() throws Exception {
loginPage.open();
loginPage.login("test-user@localhost", "password");

View file

@ -21,29 +21,26 @@
*/
package org.keycloak.testsuite.forms;
import org.junit.After;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.apache.http.HttpResponse;
import org.junit.*;
import org.keycloak.models.*;
import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.pages.AccountPasswordPage;
import org.keycloak.testsuite.pages.AccountTotpPage;
import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.*;
import org.keycloak.testsuite.pages.AppPage.RequestType;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.KeycloakRule.KeycloakSetup;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import java.util.List;
import static org.junit.Assert.assertEquals;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -77,8 +74,13 @@ public class AccountTest {
@WebResource
protected AccountTotpPage totpPage;
@WebResource
protected ErrorPage errorPage;
private TimeBasedOTP totp = new TimeBasedOTP();
private List<String> defaultRoles;
@After
public void after() {
keycloakRule.configure(new KeycloakSetup() {
@ -185,4 +187,31 @@ public class AccountTest {
Assert.assertTrue(driver.getPageSource().contains("Remove Google"));
}
@Test
public void changeProfileNoAccess() throws Exception {
try {
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
ApplicationModel app = appRealm.getApplicationNameMap().get(Constants.ACCOUNT_APPLICATION);
defaultRoles = app.getDefaultRoles();
app.updateDefaultRoles(new String[0]);
}
});
profilePage.open();
loginPage.login("test-user@localhost", "password");
Assert.assertTrue(errorPage.isCurrent());
Assert.assertEquals("No access", errorPage.getError());
} finally {
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
appRealm.getApplicationNameMap().get(org.keycloak.models.Constants.ACCOUNT_APPLICATION).updateDefaultRoles((String[]) defaultRoles.toArray(new String[0]));
}
});
}
}
}

View file

@ -93,7 +93,7 @@ public class LoginTotpTest {
}
@Test
public void loginWithTotpFailure() {
public void loginWithTotpFailure() throws Exception {
loginPage.open();
loginPage.login("test-user@localhost", "password");
@ -106,7 +106,7 @@ public class LoginTotpTest {
}
@Test
public void loginWithTotpSuccess() {
public void loginWithTotpSuccess() throws Exception {
loginPage.open();
loginPage.login("test-user@localhost", "password");

View file

@ -21,13 +21,16 @@
*/
package org.keycloak.testsuite.oauth;
import java.io.IOException;
import java.util.Map;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.representations.SkeletonKeyToken;
import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.OAuthGrantServlet;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.OAuthGrantPage;
import org.keycloak.testsuite.rule.KeycloakRule;
@ -35,8 +38,6 @@ import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.WebDriver;
import java.io.IOException;
/**
* @author <a href="mailto:vrockai@redhat.com">Viliam Rockai</a>
*/
@ -45,11 +46,6 @@ public class OAuthGrantTest {
@ClassRule
public static KeycloakRule keycloakRule = new KeycloakRule();
@BeforeClass
public static void before() {
keycloakRule.deployServlet("grant", "/grant", OAuthGrantServlet.class);
}
@Rule
public WebRule webRule = new WebRule(this);
@ -65,28 +61,13 @@ public class OAuthGrantTest {
@WebResource
protected OAuthGrantPage grantPage;
private static String GRANT_APP_URL = "http://localhost:8081/grant/";
private static String ACCESS_GRANTED = "Access rights granted.";
private static String ACCESS_NOT_GRANTED = "Access rights not granted.";
private static String ROLE_USER = "Have User privileges";
private static String ROLE_CUSTOMER = "Have Customer User privileges";
private static String GRANT_ROLE = "user";
private static String GRANT_APP = "test-app";
private static String GRANT_APP_ROLE = "customer-user";
@Test
public void oauthGrantAcceptTest() throws IOException {
driver.navigate().to(GRANT_APP_URL);
Assert.assertFalse(driver.getPageSource().contains(ACCESS_GRANTED));
Assert.assertFalse(driver.getPageSource().contains(ACCESS_NOT_GRANTED));
loginPage.isCurrent();
loginPage.login("test-user@localhost", "password");
oauth.clientId("third-party");
oauth.doLoginGrant("test-user@localhost", "password");
grantPage.assertCurrent();
Assert.assertTrue(driver.getPageSource().contains(ROLE_USER));
@ -94,23 +75,50 @@ public class OAuthGrantTest {
grantPage.accept();
Assert.assertTrue(driver.getPageSource().contains(ACCESS_GRANTED));
Assert.assertFalse(driver.getPageSource().contains(ACCESS_NOT_GRANTED));
Assert.assertTrue(oauth.getCurrentQuery().containsKey("code"));
OAuthClient.AccessTokenResponse accessToken = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get("code"), "password");
Assert.assertTrue(driver.getPageSource().contains("Role:"+ GRANT_ROLE +"."));
Assert.assertTrue(driver.getPageSource().contains("App:"+ GRANT_APP +";"+ GRANT_APP_ROLE +"."));
SkeletonKeyToken token = oauth.verifyToken(accessToken.getAccessToken());
SkeletonKeyToken.Access realmAccess = token.getRealmAccess();
Assert.assertEquals(1, realmAccess.getRoles().size());
Assert.assertTrue(realmAccess.isUserInRole("user"));
Map<String,SkeletonKeyToken.Access> resourceAccess = token.getResourceAccess();
Assert.assertEquals(1, resourceAccess.size());
Assert.assertEquals(1, resourceAccess.get("test-app").getRoles().size());
Assert.assertTrue(resourceAccess.get("test-app").isUserInRole("customer-user"));
}
@Test
public void oauthGrantAcceptTestWithScope() throws IOException {
oauth.addScope("test-app", "customer-user");
oauth.clientId("third-party");
oauth.doLoginGrant("test-user@localhost", "password");
grantPage.assertCurrent();
Assert.assertTrue(driver.getPageSource().contains(ROLE_CUSTOMER));
grantPage.accept();
Assert.assertTrue(oauth.getCurrentQuery().containsKey("code"));
OAuthClient.AccessTokenResponse accessToken = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get("code"), "password");
SkeletonKeyToken token = oauth.verifyToken(accessToken.getAccessToken());
SkeletonKeyToken.Access realmAccess = token.getRealmAccess();
Assert.assertNull(realmAccess);
Map<String,SkeletonKeyToken.Access> resourceAccess = token.getResourceAccess();
Assert.assertEquals(1, resourceAccess.size());
Assert.assertEquals(1, resourceAccess.get("test-app").getRoles().size());
Assert.assertTrue(resourceAccess.get("test-app").isUserInRole("customer-user"));
}
@Test
public void oauthGrantCancelTest() throws IOException {
driver.navigate().to(GRANT_APP_URL);
Assert.assertFalse(driver.getPageSource().contains(ACCESS_GRANTED));
Assert.assertFalse(driver.getPageSource().contains(ACCESS_NOT_GRANTED));
loginPage.isCurrent();
loginPage.login("test-user@localhost", "password");
oauth.clientId("third-party");
oauth.doLoginGrant("test-user@localhost", "password");
grantPage.assertCurrent();
Assert.assertTrue(driver.getPageSource().contains(ROLE_USER));
@ -118,7 +126,7 @@ public class OAuthGrantTest {
grantPage.cancel();
Assert.assertFalse(driver.getPageSource().contains(ACCESS_GRANTED));
Assert.assertTrue(driver.getPageSource().contains(ACCESS_NOT_GRANTED));
Assert.assertTrue(oauth.getCurrentQuery().containsKey("error"));
Assert.assertEquals("access_denied", oauth.getCurrentQuery().get("error"));
}
}

View file

@ -41,6 +41,6 @@ public abstract class AbstractPage {
abstract boolean isCurrent();
abstract void open();
abstract void open() throws Exception;
}

View file

@ -54,6 +54,7 @@ public class WebRule extends ExternalResource {
if (browser.equals("htmlunit")) {
HtmlUnitDriver d = new HtmlUnitDriver();
d.getWebClient().getOptions().setJavaScriptEnabled(true);
d.getWebClient().getOptions().setCssEnabled(false);
driver = d;
} else if (browser.equals("chrome")) {

View file

@ -78,13 +78,6 @@ public class CreateUsersWorker implements Worker {
user.setEmail(username + "@email.com");
}
// Adding default roles of realm to user
if (addDefaultRoles) {
for (RoleModel role : realm.getDefaultRoles()) {
realm.grantRole(user, role);
}
}
// Creating password (will be same as username)
if (addPassword) {
UserCredentialModel password = new UserCredentialModel();