org 2fa management for duo

This commit is contained in:
Kyle Spearrin 2018-04-02 23:19:04 -04:00
parent 08b2184e12
commit 24bf1363ab
7 changed files with 157 additions and 22 deletions

View File

@ -26,7 +26,8 @@ angular.module('bit')
duo: 2,
authenticator: 0,
email: 1,
remember: 5
remember: 5,
organizationDuo: 6
},
cipherType: {
login: 1,
@ -153,6 +154,19 @@ angular.module('bit')
requiresUsb: false
}
],
orgTwoFactorProviderInfo: [
{
type: 6,
name: 'Duo',
description: 'Verify with Duo Security using the Duo Mobile app, SMS, phone call, or U2F security key.',
enabled: false,
active: true,
image: 'duo.png',
displayOrder: 1,
priority: 0,
requiresUsb: false
}
],
plans: {
free: {
basePrice: 0,

View File

@ -2,11 +2,14 @@
.module('bit.organization')
.controller('organizationSettingsController', function ($scope, $state, apiService, toastr, authService, $uibModal,
$analytics, appSettings) {
$analytics, appSettings, constants, $filter) {
$scope.selfHosted = appSettings.selfHosted;
$scope.model = {};
$scope.twoStepProviders = constants.orgTwoFactorProviderInfo;
$scope.use2fa = false;
$scope.$on('$viewContentLoaded', function () {
apiService.organizations.get({ id: $state.params.orgId }, function (org) {
apiService.organizations.get({ id: $state.params.orgId }).$promise.then(function (org) {
$scope.model = {
name: org.Name,
billingEmail: org.BillingEmail,
@ -17,6 +20,29 @@
businessCountry: org.BusinessCountry,
businessTaxNumber: org.BusinessTaxNumber
};
$scope.use2fa = org.Use2fa;
if (org.Use2fa) {
return apiService.twoFactor.listOrganization({ orgId: $state.params.orgId }).$promise;
}
else {
return null;
}
}).then(function (response) {
if (!response || !response.Data) {
return;
}
for (var i = 0; i < response.Data.length; i++) {
if (!response.Data[i].Enabled) {
continue;
}
var provider = $filter('filter')($scope.twoStepProviders, { type: response.Data[i].Type });
if (provider.length) {
provider[0].enabled = true;
}
}
});
});
@ -56,4 +82,30 @@
controller: 'organizationDeleteController'
});
};
$scope.edit = function (provider) {
if (provider.type === constants.twoFactorProvider.organizationDuo) {
typeName = 'Duo';
}
else {
return;
}
var modal = $uibModal.open({
animation: true,
templateUrl: 'app/settings/views/settingsTwoStep' + typeName + '.html',
controller: 'settingsTwoStep' + typeName + 'Controller',
resolve: {
enabled: function () { return provider.enabled; },
orgId: function () { return $state.params.orgId; }
}
});
modal.result.then(function (enabled) {
if (enabled || enabled === false) {
// do not adjust when undefined or null
provider.enabled = enabled;
}
});
};
});

View File

@ -63,6 +63,36 @@
</div>
</form>
</div>
<div class="box box-default" ng-if="use2fa">
<div class="box-header with-border">
<h3 class="box-title">Two-step Login Providers</h3>
</div>
<div class="box-body no-padding">
<div class="table-responsive">
<table class="table table-striped table-hover table-vmiddle">
<tbody>
<tr ng-repeat="provider in twoStepProviders | orderBy: 'displayOrder'">
<td style="width: 120px; height: 75px;" align="center">
<a href="#" stop-click ng-click="edit(provider)">
<img alt="{{::provider.name}}" ng-src="{{'images/two-factor/' + provider.image}}" />
</a>
</td>
<td>
<a href="#" stop-click ng-click="edit(provider)">{{::provider.name}}</a>
<div class="text-muted text-sm">{{::provider.description}}</div>
</td>
<td style="width: 100px;" class="text-right">
<span class="label label-full"
ng-class="{ 'label-success': provider.enabled, 'label-default': !provider.enabled }">
{{provider.enabled ? 'Enabled' : 'Disabled'}}
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="box box-default">
<div class="box-header with-border">
<h3 class="box-title">Import/Export</h3>

View File

@ -160,9 +160,11 @@
_service.twoFactor = $resource(_apiUri + '/two-factor', {}, {
list: { method: 'GET', params: {} },
listOrganization: { url: _apiUri + '/organizations/:orgId/two-factor', method: 'GET', params: { orgId: '@orgId' } },
getEmail: { url: _apiUri + '/two-factor/get-email', method: 'POST', params: {} },
getU2f: { url: _apiUri + '/two-factor/get-u2f', method: 'POST', params: {} },
getDuo: { url: _apiUri + '/two-factor/get-duo', method: 'POST', params: {} },
getOrganizationDuo: { url: _apiUri + '/organizations/:orgId/two-factor/get-duo', method: 'POST', params: { orgId: '@orgId' } },
getAuthenticator: { url: _apiUri + '/two-factor/get-authenticator', method: 'POST', params: {} },
getYubi: { url: _apiUri + '/two-factor/get-yubikey', method: 'POST', params: {} },
sendEmail: { url: _apiUri + '/two-factor/send-email', method: 'POST', params: {} },
@ -171,8 +173,10 @@
putU2f: { url: _apiUri + '/two-factor/u2f', method: 'POST', params: {} },
putAuthenticator: { url: _apiUri + '/two-factor/authenticator', method: 'POST', params: {} },
putDuo: { url: _apiUri + '/two-factor/duo', method: 'POST', params: {} },
putOrganizationDuo: { url: _apiUri + '/organizations/:orgId/two-factor/duo', method: 'POST', params: { orgId: '@orgId' } },
putYubi: { url: _apiUri + '/two-factor/yubikey', method: 'POST', params: {} },
disable: { url: _apiUri + '/two-factor/disable', method: 'POST', params: {} },
disableOrganization: { url: _apiUri + '/organizations/:orgId/two-factor/disable', method: 'POST', params: { orgId: '@orgId' } },
recover: { url: _apiUri + '/two-factor/recover', method: 'POST', params: {} },
getRecover: { url: _apiUri + '/two-factor/get-recover', method: 'POST', params: {} }
});

View File

@ -152,6 +152,7 @@ angular
useGroups: profile.Organizations[i].UseGroups,
useDirectory: profile.Organizations[i].UseDirectory,
useEvents: profile.Organizations[i].UseEvents,
use2fa: profile.Organizations[i].Use2fa,
useTotp: profile.Organizations[i].UseTotp
};
}
@ -187,6 +188,7 @@ angular
useGroups: org.UseGroups,
useDirectory: org.UseDirectory,
useEvents: org.UseEvents,
use2fa: org.Use2fa,
useTotp: org.UseTotp
};
profile.organizations[o.id] = o;

View File

@ -60,7 +60,8 @@
templateUrl: 'app/settings/views/settingsTwoStep' + typeName + '.html',
controller: 'settingsTwoStep' + typeName + 'Controller',
resolve: {
enabled: function () { return provider.enabled; }
enabled: function () { return provider.enabled; },
orgId: function () { return null; }
}
});

View File

@ -2,7 +2,7 @@
.module('bit.settings')
.controller('settingsTwoStepDuoController', function ($scope, apiService, $uibModalInstance, cryptoService,
toastr, $analytics, constants, $timeout) {
toastr, $analytics, constants, $timeout, orgId) {
$analytics.eventTrack('settingsTwoStepDuoController', { category: 'Modal' });
var _masterPasswordHash;
@ -20,9 +20,16 @@
$scope.auth = function (model) {
$scope.authPromise = cryptoService.hashPassword(model.masterPassword).then(function (hash) {
_masterPasswordHash = hash;
return apiService.twoFactor.getDuo({}, {
var requestModel = {
masterPasswordHash: _masterPasswordHash
}).$promise;
};
if (orgId) {
return apiService.twoFactor.getOrganizationDuo({ orgId: orgId }, requestModel).$promise;
}
else {
return apiService.twoFactor.getDuo({}, requestModel).$promise;
}
}).then(function (apiResponse) {
processResult(apiResponse);
$scope.authed = true;
@ -43,27 +50,52 @@
return;
}
$scope.submitPromise = apiService.twoFactor.disable({}, {
masterPasswordHash: _masterPasswordHash,
type: constants.twoFactorProvider.duo
}, function (response) {
$analytics.eventTrack('Disabled Two-step Duo');
toastr.success('Duo has been disabled.');
$scope.enabled = response.Enabled;
$scope.close();
}).$promise;
if (orgId) {
$scope.submitPromise = apiService.twoFactor.disableOrganization({ orgId: orgId }, {
masterPasswordHash: _masterPasswordHash,
type: constants.twoFactorProvider.organizationDuo
}, function (response) {
$analytics.eventTrack('Disabled Two-step Organization Duo');
toastr.success('Duo has been disabled.');
$scope.enabled = response.Enabled;
$scope.close();
}).$promise;
}
else {
$scope.submitPromise = apiService.twoFactor.disable({}, {
masterPasswordHash: _masterPasswordHash,
type: constants.twoFactorProvider.duo
}, function (response) {
$analytics.eventTrack('Disabled Two-step Duo');
toastr.success('Duo has been disabled.');
$scope.enabled = response.Enabled;
$scope.close();
}).$promise;
}
}
function update(model) {
$scope.submitPromise = apiService.twoFactor.putDuo({}, {
var requestModel = {
integrationKey: model.ikey,
secretKey: model.skey,
host: model.host,
masterPasswordHash: _masterPasswordHash
}, function (response) {
$analytics.eventTrack('Enabled Two-step Duo');
processResult(response);
}).$promise;
};
if (orgId) {
$scope.submitPromise = apiService.twoFactor.putOrganizationDuo({ orgId: orgId }, requestModel,
function (response) {
$analytics.eventTrack('Enabled Two-step Organization Duo');
processResult(response);
}).$promise;
}
else {
$scope.submitPromise = apiService.twoFactor.putDuo({}, requestModel,
function (response) {
$analytics.eventTrack('Enabled Two-step Duo');
processResult(response);
}).$promise;
}
}
function processResult(response) {
@ -80,7 +112,7 @@
closing = true;
$uibModalInstance.close($scope.enabled);
};
$scope.$on('modal.closing', function (e, reason, closed) {
if (closing) {
return;