Access Control config enhancements

- Check Github API and automatically remove users/orgs that don't exist
- Saving/Saved state on the authorization save button to show state
- Waiting state on test button to show state
- Size GitHub popup window bigger to show content
This commit is contained in:
Vincent Fiduccia 2015-01-13 15:21:23 -07:00
parent 1c0a29899b
commit b4940855e8
11 changed files with 127 additions and 54 deletions

View File

@ -8,8 +8,8 @@ export function initialize(container, application) {
// Find out if auth is enabled // Find out if auth is enabled
store.rawRequest({ store.rawRequest({
url: 'token', // Base url, which will be /v1 url: 'token',
headers: { 'authorization': undefined } headers: { 'authorization': undefined } // Explicitly not send the auth token
}) })
.then(function(obj) { .then(function(obj) {
var body = JSON.parse(obj.xhr.responseText); var body = JSON.parse(obj.xhr.responseText);

View File

@ -5,21 +5,24 @@ export default Ember.Component.extend({
login: null, login: null,
classNames: ['gh-avatar'], classNames: ['gh-avatar'],
name: 'Loading...', name: 'Checking...',
avatarUrl: null, avatarUrl: null,
nameChanged: function() { nameChanged: function() {
var self = this; var self = this;
var url = 'https://api.github.com/' + this.get('type') + 's/' + this.get('login') + '?s=40'; var login = this.get('login');
var type = this.get('type');
var url = 'https://api.github.com/' + type + 's/' + login;
Ember.$.ajax({url: url, dataType: 'json'}).then(function(body) { Ember.$.ajax({url: url, dataType: 'json'}).then(function(body) {
self.set('name', body.name); self.set('name', body.name);
self.set('avatarUrl', body.avatar_url);
}, function() {
var type = self.get('type');
type = type.substr(0,1).toUpperCase() + type.substr(1);
self.set('name', 'Warning: ' + type + ' not found'); var avatarUrl = body.avatar_url;
avatarUrl += (avatarUrl.indexOf('?') >= 0 ? '&' : '?') + 's=40';
self.set('avatarUrl', avatarUrl);
}, function() {
self.set('name', '(Not found)');
self.sendAction('notFound', login);
}); });
}.observes('login','type').on('init'), }.observes('login','type').on('init'),

View File

@ -12,5 +12,5 @@
<div class="clip"> <div class="clip">
<a {{bind-attr href=url}} target="_blank">{{login}}</a> <a {{bind-attr href=url}} target="_blank">{{login}}</a>
</div> </div>
<div class="text-muted clip">{{name}}</div> <div class="text-muted clip">{{#if name}}{{name}}{{else}}<i>No name</i>{{/if}}</div>
</div> </div>

View File

@ -2,10 +2,13 @@
{{#if app.authenticationEnabled}} {{#if app.authenticationEnabled}}
<div class="pull-right"> <div class="pull-right">
<div class="dropdown"> <div class="dropdown">
<span class="fa-stack fa-lg hand" id="user-dropdown" data-toggle="dropdown" aria-expanded="true"> <span class="hand" id="user-dropdown" data-toggle="dropdown" aria-expanded="true">
<span class="fa-stack fa-lg">
<i class="fa fa-circle-o fa-stack-2x"></i> <i class="fa fa-circle-o fa-stack-2x"></i>
<i class="fa fa-user fa-stack-1x"></i> <i class="fa fa-user fa-stack-1x"></i>
</span> </span>
<i class="fa fa-chevron-down"></i>
</span>
<ul class="dropdown-menu dropdown-menu-right" role="menu" aria-labelledby="user-dropdown"> <ul class="dropdown-menu dropdown-menu-right" role="menu" aria-labelledby="user-dropdown">
<li role="presentation" class="disabled"> <li role="presentation" class="disabled">
<a role="menuitem" tabindex="-1">Settings</a> <a role="menuitem" tabindex="-1">Settings</a>

View File

@ -41,7 +41,7 @@ export default Ember.Controller.extend({
self.set('timedOut', false); self.set('timedOut', false);
self.set('waiting', true); self.set('waiting', true);
self.get('torii').open('github-oauth2').then(function(github){ self.get('torii').open('github-oauth2',{width: 1024, height: 500}).then(function(github){
return self.get('store').rawRequest({ return self.get('store').rawRequest({
url: 'token', url: 'token',
method: 'POST', method: 'POST',

View File

@ -3,12 +3,17 @@ import Ember from 'ember';
export default Ember.ObjectController.extend({ export default Ember.ObjectController.extend({
confirmDisable: false, confirmDisable: false,
error: null, error: null,
testing: false,
saving: false,
saved: true,
createIncomplete: function() { createDisabled: function() {
var id = (this.get('clientId')||'').trim(); var id = (this.get('clientId')||'').trim();
var secret = (this.get('clientSecret')||'').trim(); var secret = (this.get('clientSecret')||'').trim();
return id.length < 20 ||secret.length < 40; return id.length < 20 ||secret.length < 40 || this.get('testing');
}.property('clientId','clientSecret'), }.property('clientId','clientSecret','testing'),
saveDisabled: Ember.computed.or('saving','saved'),
destinationUrl: function() { destinationUrl: function() {
return window.location.origin+'/'; return window.location.origin+'/';
@ -17,8 +22,10 @@ export default Ember.ObjectController.extend({
actions: { actions: {
test: function() { test: function() {
var self = this; var self = this;
self.send('clearError');
self.set('testing',true);
var model = this.get('model'); var model = self.get('model');
model.set('clientId', model.get('clientId').trim()); model.set('clientId', model.get('clientId').trim());
model.set('clientSecret', model.get('clientSecret').trim()); model.set('clientSecret', model.get('clientSecret').trim());
model.set('enabled',false); // It should already be, but just in case.. model.set('enabled',false); // It should already be, but just in case..
@ -34,7 +41,8 @@ export default Ember.ObjectController.extend({
authenticate: function() { authenticate: function() {
var self = this; var self = this;
self.get('torii').open('github-oauth2').then(function(github){ self.send('clearError');
self.get('torii').open('github-oauth2',{width: 1024, height: 500}).then(function(github){
return self.get('store').rawRequest({ return self.get('store').rawRequest({
url: 'token', url: 'token',
method: 'POST', method: 'POST',
@ -52,18 +60,21 @@ export default Ember.ObjectController.extend({
.catch(function(err) { .catch(function(err) {
// Github auth failed.. try again // Github auth failed.. try again
self.send('gotError', err); self.send('gotError', err);
})
.finally(function() {
self.set('testing',false);
}); });
}, },
authenticationSucceeded: function(auth) { authenticationSucceeded: function(auth) {
console.log('Authentication succeeded');
var self = this; var self = this;
var session = self.get('session'); self.send('clearError');
var session = self.get('session');
session.set('token', auth.jwt); session.set('token', auth.jwt);
session.set('isLoggedIn',1); session.set('isLoggedIn',1);
var model = this.get('model'); var model = self.get('model');
model.set('enabled',true); model.set('enabled',true);
model.set('allowOrganizations', auth.orgs||[]); model.set('allowOrganizations', auth.orgs||[]);
model.set('allowUsers', [auth.user]); model.set('allowUsers', [auth.user]);
@ -75,7 +86,6 @@ export default Ember.ObjectController.extend({
}, },
waitAndRefresh: function(expect,limit) { waitAndRefresh: function(expect,limit) {
console.log('Wait and refresh',expect,limit);
var self = this; var self = this;
if ( limit === undefined ) if ( limit === undefined )
{ {
@ -114,6 +124,9 @@ export default Ember.ObjectController.extend({
}, },
addUser: function() { addUser: function() {
this.send('clearError');
this.set('saved',false);
var str = (this.get('addUser')||'').trim(); var str = (this.get('addUser')||'').trim();
if ( str ) if ( str )
{ {
@ -122,11 +135,15 @@ export default Ember.ObjectController.extend({
} }
}, },
removeUser: function(user) { removeUser: function(login) {
this.get('allowUsers').removeObject(user); this.set('saved',false);
this.get('allowUsers').removeObject(login);
}, },
addOrg: function() { addOrg: function() {
this.send('clearError');
this.set('saved',false);
var str = (this.get('addOrg')||'').trim(); var str = (this.get('addOrg')||'').trim();
if ( str ) if ( str )
{ {
@ -135,17 +152,34 @@ export default Ember.ObjectController.extend({
} }
}, },
removeOrg: function(org) { removeOrg: function(login) {
this.get('allowOrganizations').removeObject(org); this.set('saved',false);
this.get('allowOrganizations').removeObject(login);
},
userNotFound: function(login) {
this.send('showError',"User '"+ login + "' not found");
this.send('removeUser',login);
},
orgNotFound: function(login) {
this.send('showError',"Organization '"+ login + "' not found");
this.send('removeOrg',login);
}, },
saveAuthorization: function() { saveAuthorization: function() {
var self = this; var self = this;
self.send('clearError');
self.set('saving',true);
self.set('saved',false);
var model = self.get('model'); var model = self.get('model');
model.save().then(function() { model.save().then(function() {
self.send('waitAndRefresh', true); self.set('saved',true);
}).catch(function(err) { }).catch(function(err) {
self.send('gotError', err); self.send('gotError', err);
}).finally(function() {
self.set('saving',false);
}); });
}, },
@ -154,12 +188,21 @@ export default Ember.ObjectController.extend({
}, },
gotError: function(err) { gotError: function(err) {
this.set('error', err.message); this.send('showError', err.message);
},
showError: function(msg) {
this.set('error', msg);
window.scrollY = 0; window.scrollY = 0;
}, },
clearError: function() {
this.set('error', '');
},
disable: function() { disable: function() {
var self = this; var self = this;
self.send('clearError');
var model = this.get('model'); var model = this.get('model');
model.set('allowOrganizations',[]); model.set('allowOrganizations',[]);

View File

@ -11,5 +11,9 @@ export default Ember.Route.extend(AuthenticatedRouteMixin,{
setupController: function(controller, model) { setupController: function(controller, model) {
this._super(controller,model); this._super(controller,model);
controller.set('confirmDisable',false); controller.set('confirmDisable',false);
controller.set('saving',false);
controller.set('saved',true);
controller.set('testing',false);
controller.set('error',null);
} }
}); });

View File

@ -1,23 +1,29 @@
<div style="padding: 20px 20px 0 20px;"> <section>
{{#if error}}
<div class="alert alert-danger">
<i style="float: left;" class="fa fa-lg fa-exclamation-circle"></i>
<p style="margin-left: 50px">{{error}}</p>
</div>
{{/if}}
<div class="well"> <div class="well">
<h2>Access Control is {{#if app.authenticationEnabled}}<b>enabled</b>{{else}}<b class="text-warning">not configured</b>{{/if}}</h2> <h2>Access Control is {{#if app.authenticationEnabled}}<b>enabled</b>{{else}}<b class="text-warning">not configured</b>{{/if}}</h2>
<p> <div>
{{#if app.authenticationEnabled}} {{#if app.authenticationEnabled}}
Rancher is configured to restrict access to the GitHub users and organization members below. Rancher is configured to restrict access to the GitHub users and organization members below.
{{else}} {{else}}
Rancher can be configured to restrict access to a set of GitHub users and organization members. This is not currently set up, so anybody that reach this page (or the API) has full control over the system. Rancher can be configured to restrict access to a set of GitHub users and organization members. This is not currently set up, so anybody that reach this page (or the API) has full control over the system.
{{/if}} {{/if}}
</p>
</div> </div>
{{#if error}}
<div class="alert alert-danger">
<i style="float: left;" class="fa fa-exclamation-circle"></i>
<p style="margin-left: 50px">{{error}}</p>
</div> </div>
{{/if}}
{{#if app.authenticationEnabled}} {{#if app.authenticationEnabled}}
<div class="well">
<h4>Authentication</h4>
<hr/>
<p>To change the configured GitHub application, disable access control below and then set it up again.</p>
<div><b>Client ID: </b> <span class="text-muted">{{clientId}}</span></div>
</div>
<div class="well"> <div class="well">
<h4>Configure Authorization</h4> <h4>Configure Authorization</h4>
@ -27,7 +33,7 @@
<div class="col-sm-6"> <div class="col-sm-6">
<label>Users</label> <label>Users</label>
<form {{action "addUser"}}> <form {{action "addUser" on="submit"}}>
<div class="input-group"> <div class="input-group">
{{input type="text" value=addUser placeholder="Add GitHub username" class="form-control"}} {{input type="text" value=addUser placeholder="Add GitHub username" class="form-control"}}
<div class="input-group-btn"> <div class="input-group-btn">
@ -41,7 +47,7 @@
<ul class="list-unstyled gh-avatar-list"> <ul class="list-unstyled gh-avatar-list">
{{#each user in allowUsers}} {{#each user in allowUsers}}
<li> <li>
{{#github-avatar type="user" login=user}} {{#github-avatar type="user" login=user notFound="userNotFound"}}
<button class="btn btn-danger btn-sm pull-right gh-action" {{action "removeUser" user}}>Remove</button> <button class="btn btn-danger btn-sm pull-right gh-action" {{action "removeUser" user}}>Remove</button>
{{/github-avatar}} {{/github-avatar}}
</li> </li>
@ -54,7 +60,7 @@
<div class="col-sm-6"> <div class="col-sm-6">
<label>Organizations</label> <label>Organizations</label>
<form {{action "addOrg"}}> <form {{action "addOrg" on="submit"}}>
<div class="input-group"> <div class="input-group">
{{input type="text" value=addOrg placeholder="Add GitHub organization name" class="form-control"}} {{input type="text" value=addOrg placeholder="Add GitHub organization name" class="form-control"}}
<div class="input-group-btn"> <div class="input-group-btn">
@ -68,7 +74,7 @@
<ul class="list-unstyled gh-avatar-list"> <ul class="list-unstyled gh-avatar-list">
{{#each org in allowOrganizations}} {{#each org in allowOrganizations}}
<li> <li>
{{#github-avatar type="org" login=org}} {{#github-avatar type="org" login=org notFound="orgNotFound"}}
<button class="btn btn-danger btn-sm pull-right gh-action" {{action "removeOrg" org}}>Remove</button> <button class="btn btn-danger btn-sm pull-right gh-action" {{action "removeOrg" org}}>Remove</button>
{{/github-avatar}} {{/github-avatar}}
</li> </li>
@ -81,8 +87,16 @@
<hr/> <hr/>
<button class="btn btn-primary" {{action "saveAuthorization"}}> <button class="btn btn-primary" {{bind-attr disabled=saveDisabled}} {{action "saveAuthorization"}}>
Save Authorization Configuration {{#if saved}}
Saved
{{else}}
{{#if saving}}
<i class="fa fa-spinner fa-spin"></i> Saving...</i>
{{else}}
Save authorization configuration
{{/if}}
{{/if}}
</button> </button>
</div> </div>
@ -96,11 +110,11 @@
{{#if confirmDisable}} {{#if confirmDisable}}
<button class="btn btn-danger" {{action "disable"}}> <button class="btn btn-danger" {{action "disable"}}>
<i class="fa fa-fire"></i> Are you sure? Click again to really disable Access Control <i class="fa fa-fire"></i> Are you sure? Click again to really disable access control
</button> </button>
{{else}} {{else}}
<button class="btn btn-danger" {{action "promptDisable"}}> <button class="btn btn-danger" {{action "promptDisable"}}>
<i class="fa fa-fire"></i> Disable Access Control <i class="fa fa-fire"></i> Disable access control
</button> </button>
{{/if}} {{/if}}
@ -160,11 +174,15 @@
<p>Check that your application is configured correctly by testing authentication with it:</p> <p>Check that your application is configured correctly by testing authentication with it:</p>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<button {{bind-attr disabled=createIncomplete}} class="btn btn-primary" {{action "test"}}> <button {{bind-attr disabled=createDisabled}} class="btn btn-primary" {{action "test"}}>
{{#if testing}}
<i class="fa fa-spinner fa-spin"></i> Waiting to hear back from GitHub
{{else}}
<i class="fa fa-github"></i> Authenticate with GitHub <i class="fa fa-github"></i> Authenticate with GitHub
{{/if}}
</button> </button>
</div> </div>
</div> </div>
</div> </div>
{{/unless}} {{/unless}}
</div> </section>

View File

@ -1,4 +1,7 @@
import Ember from 'ember'; import Ember from 'ember';
export default Ember.Route.extend({ export default Ember.Route.extend({
enter: function() {
this.send('setPageName','Settings');
},
}); });

View File

@ -116,13 +116,12 @@ HEADER {
z-index: 2; z-index: 2;
margin-left: $nav_width; margin-left: $nav_width;
background-color: $header_bg; background-color: $header_bg;
padding: 0; padding: 0 20px;
H2 { H2 {
font-weight: normal; font-weight: normal;
margin: 0; margin: 0;
line-height: $header_height; line-height: $header_height;
padding-left: 20px;
} }
} }

View File

@ -19,6 +19,7 @@
"license": "Apache 2", "license": "Apache 2",
"devDependencies": { "devDependencies": {
"ember-api-store": "1.0.7", "ember-api-store": "1.0.7",
"rancher-torii": "^0.2.2",
"body-parser": "^1.2.0", "body-parser": "^1.2.0",
"broccoli-asset-rev": "^1.0.0", "broccoli-asset-rev": "^1.0.0",
@ -38,7 +39,6 @@
"express": "^4.8.5", "express": "^4.8.5",
"forever-agent": "^0.5.2", "forever-agent": "^0.5.2",
"glob": "^4.0.5", "glob": "^4.0.5",
"http-proxy": "^1.6.2", "http-proxy": "^1.6.2"
"torii": "^0.2.2"
} }
} }