Skip to content

Commit

Permalink
fix(auth): setupSecurity now is a complete setup process for tokens.
Browse files Browse the repository at this point in the history
  • Loading branch information
rodrigorodriguez committed Jan 31, 2019
1 parent cfe5cd2 commit 4718fe4
Show file tree
Hide file tree
Showing 12 changed files with 196 additions and 102 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
| Releases | [![General Bots](https://img.shields.io/npm/dt/botserver.svg?logo=npm&label=botserver)](https://www.npmjs.com/package/botserver/) [![.gbapp lib](https://img.shields.io/npm/dt/botlib.svg?logo=npm&label=botlib)](https://www.npmjs.com/package/botlib/) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)|
| [Docker Image](https://github.com/lpicanco/docker-botserver) | ![Docker Automated build](https://img.shields.io/docker/automated/lpicanco/botserver.svg) ![Docker Build Status](https://img.shields.io/docker/build/lpicanco/botserver.svg) ![MicroBadger Size](https://img.shields.io/microbadger/image-size/lpicanco/botserver.svg) ![MicroBadger Layers](https://img.shields.io/microbadger/layers/lpicanco/botserver.svg) ![Docker Pulls](https://img.shields.io/docker/pulls/lpicanco/botserver.svg) <br/> *Provided by [@lpicanco](https://github.com/lpicanco/docker-botserver)* |


#### Watch a video about easeness authoring of bot packages, development environment and self-deployment

* Now with General Bots you can press F5 on Visual Studio to get a bot factory on your environment* published on November 10th, 2018.
* Now with the General Bots server you can press F5 on Visual Studio to get a bot factory on your environment* published on November 10th, 2018.

[![General Bot Video](https://raw.githubusercontent.com/pragmatismo-io/BotServer/master/docs/images/video-01-thumb.jpg)](https://www.youtube.com/watch?v=AfKTwljoMOs)

Expand All @@ -20,7 +21,7 @@ Welcome to General Bot Community Edition

![General Bot Logo](https://raw.githubusercontent.com/pragmatismo-io/BotServer/master/logo.png)

General Bot is a package based chat bot server focused in convention over configuration and code-less approaches, which brings software packages and application server concepts to help parallel bot development.
General Bot is a strongly typed package based chat bot server focused in convention over configuration and code-less approaches, which brings software packages and application server concepts to help parallel bot development.


## Sample Package #1: [default.gbdialog (VBA)](https://github.com/pragmatismo-io/BotServer/tree/master/packages/default.gbdialog)
Expand Down
101 changes: 80 additions & 21 deletions packages/admin.gbapp/dialogs/AdminDialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@

const UrlJoin = require('url-join');
import { BotAdapter } from 'botbuilder';
import { WaterfallDialog } from 'botbuilder-dialogs';
import { WaterfallDialog, WaterfallStep, WaterfallStepContext } from 'botbuilder-dialogs';
import { GBMinInstance } from 'botlib';
import { IGBDialog } from 'botlib';
import { GBConfigService } from '../../core.gbapp/services/GBConfigService';
Expand All @@ -60,28 +60,35 @@ export class AdminDialog extends IGBDialog {
await deployer.undeployPackageFromLocalPath(min.instance, UrlJoin('packages', packageName));
}

public static isSharePointPath(path: string) {
return path.indexOf('sharepoint.com') > 0;
}

public static async deployPackageCommand(min: GBMinInstance, text: string, deployer: GBDeployer) {
const packageName = text.split(' ')[1];
const additionalPath = GBConfigService.get('ADDITIONAL_DEPLOY_PATH');
if (!additionalPath)
{
throw new Error('ADDITIONAL_DEPLOY_PATH is not set and deployPackage was called.');

if (AdminDialog.isSharePointPath(packageName)) {
await deployer.deployFromSharePoint(min.instance.instanceId, packageName);
} else {
const additionalPath = GBConfigService.get('ADDITIONAL_DEPLOY_PATH');
if (!additionalPath) {
throw new Error('ADDITIONAL_DEPLOY_PATH is not set and deployPackage was called.');
}
await deployer.deployPackageFromLocalPath(min, UrlJoin(additionalPath, packageName));
}
await deployer.deployPackageFromLocalPath(min, UrlJoin(additionalPath, packageName));
}

public static async rebuildIndexPackageCommand(min: GBMinInstance, text: string, deployer: GBDeployer) {
await deployer.rebuildIndex(min.instance);
}

public static async addConnectionCommand(min: GBMinInstance, text: any) {
public static async addConnectionCommand(min: GBMinInstance, text: any) {
const packageName = text.split(' ')[1];
const importer = new GBImporter(min.core);
const admin = new GBAdminService(min.core);
// TODO: await admin.addConnection
}


/**
* Setup dialogs flows and define services call.
*
Expand All @@ -94,6 +101,8 @@ export class AdminDialog extends IGBDialog {
const importer = new GBImporter(min.core);
const deployer = new GBDeployer(min.core, importer);

AdminDialog.setupSecurityDialogs(min);

min.dialogs.add(
new WaterfallDialog('/admin', [
async step => {
Expand Down Expand Up @@ -151,15 +160,15 @@ export class AdminDialog extends IGBDialog {

return await step.replaceDialog('/admin', { firstRun: false });
} else if (cmdName === 'setupSecurity') {
await AdminDialog.setupSecurity(min, step);
return await step.beginDialog('/setupSecurity');
} else {
unknownCommand = true;
}

if (unknownCommand) {
await step.context.sendActivity(Messages[locale].unknown_command);
} else {
await step.context.sendActivity(Messages[locale].finshed_working(cmdName));
await step.context.sendActivity(Messages[locale].finished_working);
}
await step.endDialog();

Expand All @@ -169,16 +178,66 @@ export class AdminDialog extends IGBDialog {
);
}

private static async setupSecurity(min: any, step: any) {
const locale = step.activity.locale;
const state = `${min.instance.instanceId}${Math.floor(Math.random() * 1000000000)}`;
await min.adminService.setValue(min.instance.instanceId, 'AntiCSRFAttackState', state);
const url = `https://login.microsoftonline.com/${min.instance.authenticatorTenant}/oauth2/authorize?client_id=${
min.instance.authenticatorClientId
}&response_type=code&redirect_uri=${min.instance.botEndpoint}/${
min.instance.botId
}/token&state=${state}&response_mode=query`;

await step.sendActivity(Messages[locale].consent(url));
private static setupSecurityDialogs(min: any) {
min.dialogs.add(
new WaterfallDialog('/setupSecurity', [
async step => {
const locale = step.context.activity.locale;
const prompt = Messages[locale].enter_authenticator_tenant;

return await step.prompt('textPrompt', prompt);
},
async step => {
step.activeDialog.state.authenticatorTenant = step.result;
const locale = step.context.activity.locale;
const prompt = Messages[locale].enter_authenticator_authority_host_url;

return await step.prompt('textPrompt', prompt);
},
async step => {
step.activeDialog.state.authenticatorAuthorityHostUrl = step.result;
const locale = step.context.activity.locale;
const prompt = Messages[locale].enter_authenticator_client_id;

return await step.prompt('textPrompt', prompt);
},
async step => {
step.activeDialog.state.authenticatorClientId = step.result;
const locale = step.context.activity.locale;
const prompt = Messages[locale].enter_authenticator_client_secret;

return await step.prompt('textPrompt', prompt);
},
async step => {
step.activeDialog.state.authenticatorClientSecret = step.result;

await min.adminService.updateSecurityInfo(
min.instance.instanceId,
step.activeDialog.state.authenticatorTenant,
step.activeDialog.state.authenticatorAuthorityHostUrl,
step.activeDialog.state.authenticatorClientId,
step.activeDialog.state.authenticatorClientSecret
);

const locale = step.context.activity.locale;
const state = `${min.instance.instanceId}${Math.floor(Math.random() * 1000000000)}`;

await min.adminService.setValue(min.instance.instanceId, 'AntiCSRFAttackState', state);

const url = `https://login.microsoftonline.com/${min.instance.authenticatorTenant}/oauth2/authorize?client_id=${
min.instance.authenticatorClientId
}&response_type=code&redirect_uri=${UrlJoin(
min.instance.botEndpoint,
min.instance.botId,
'/token'
)}&state=${state}&response_mode=query`;

await step.context.sendActivity(Messages[locale].consent(url));

return await step.replaceDialog('/ask', {isReturning: true});
}
])
);
}

}
5 changes: 3 additions & 2 deletions packages/admin.gbapp/models/AdminModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ import {
CreatedAt,
Model,
Table,
UpdatedAt
UpdatedAt,
DataType
} from 'sequelize-typescript';

@Table
Expand All @@ -53,7 +54,7 @@ export class GuaribasAdmin extends Model<GuaribasAdmin> {
@Column
public key: string;

@Column
@Column(DataType.STRING(1024))
public value: string;

@Column
Expand Down
80 changes: 37 additions & 43 deletions packages/admin.gbapp/services/GBAdminService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,20 @@

import { AuthenticationContext, TokenResponse } from 'adal-node';
import { IGBCoreService } from 'botlib';
import { GuaribasInstance } from '../../core.gbapp/models/GBModel';
import { GuaribasAdmin } from '../models/AdminModel';
const UrlJoin = require('url-join');
const msRestAzure = require('ms-rest-azure');
const PasswordGenerator = require('strict-password-generator').default;

/**
* Services for server administration.
*/
export class GBAdminService {

public static GB_PROMPT: string = 'GeneralBots: ';
public static masterBotInstanceId = 0;

public static StrongRegex = new RegExp(
'^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*+_-])(?=.{8,})'
);
public static StrongRegex = new RegExp('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*+_-])(?=.{8,})');

public core: IGBCoreService;

Expand All @@ -62,26 +63,16 @@ export class GBAdminService {
return msRestAzure.generateUuid();
}

public static async getADALTokenFromUsername(
username: string,
password: string
) {
const credentials = await GBAdminService.getADALCredentialsFromUsername(
username,
password
);
public static async getADALTokenFromUsername(username: string, password: string) {
const credentials = await GBAdminService.getADALCredentialsFromUsername(username, password);
const accessToken = credentials.tokenCache._entries[0].accessToken;

return accessToken;
}

public static async getADALCredentialsFromUsername(
username: string,
password: string
) {
const credentials = await msRestAzure.loginWithUsernamePassword(
username,
password
);
public static async getADALCredentialsFromUsername(username: string, password: string) {
const credentials = await msRestAzure.loginWithUsernamePassword(username, password);

return credentials;
}

Expand Down Expand Up @@ -114,31 +105,47 @@ export class GBAdminService {
return name;
}

public async setValue(
instanceId: number,
key: string,
value: string
): Promise<GuaribasAdmin> {
public async setValue(instanceId: number, key: string, value: string): Promise<GuaribasAdmin> {
const options = { where: {} };
options.where = { key: key };
let admin = await GuaribasAdmin.findOne(options);
if (admin == null) {
if (admin === null) {
admin = new GuaribasAdmin();
admin.key = key;
}
admin.value = value;
admin.instanceId = instanceId;

return admin.save();
}

public async updateSecurityInfo(
instanceId: number,
authenticatorTenant: string,
authenticatorAuthorityHostUrl: string,
authenticatorClientId: string,
authenticatorClientSecret: string
): Promise<GuaribasInstance> {
const options = { where: {} };
options.where = { instanceId: instanceId };
const item = await GuaribasInstance.findOne(options);
item.authenticatorTenant = authenticatorTenant;
item.authenticatorAuthorityHostUrl = authenticatorAuthorityHostUrl;
item.authenticatorClientId = authenticatorClientId;
item.authenticatorClientSecret = authenticatorClientSecret;

return item.save();
}

public async getValue(instanceId: number, key: string) {
const options = { where: {} };
options.where = { key: key, instanceId: instanceId };
const obj = await GuaribasAdmin.findOne(options);

return Promise.resolve(obj.value);
}

public async acquireElevatedToken(instanceId): Promise<string> {
public async acquireElevatedToken(instanceId: number): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
const instance = await this.core.loadInstanceById(instanceId);

Expand Down Expand Up @@ -166,27 +173,14 @@ export class GBAdminService {
reject(err);
} else {
const token = res as TokenResponse;
await this.setValue(
instanceId,
'accessToken',
token.accessToken
);
await this.setValue(
instanceId,
'refreshToken',
token.refreshToken
);
await this.setValue(
instanceId,
'expiresOn',
token.expiresOn.toString()
);
await this.setValue(instanceId, 'accessToken', token.accessToken);
await this.setValue(instanceId, 'refreshToken', token.refreshToken);
await this.setValue(instanceId, 'expiresOn', token.expiresOn.toString());
resolve(token.accessToken);
}
}
);
}
});
}

}
8 changes: 6 additions & 2 deletions packages/admin.gbapp/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export const Messages = {
welcome: 'Welcome to Pragmatismo.io GeneralBots Administration.',
which_task: 'Which task do you wanna run now?',
working: (command) => `I'm working on ${command}...`,
finshed_working: 'Done.',
finished_working: 'Done.',
unknown_command: text =>
`Well, but ${text} is not a administrative General Bots command, I will try to search for it.`,
hi: text => `Hello, ${text}.`,
Expand All @@ -13,7 +13,11 @@ export const Messages = {
redeployPackage: text => `Redeploying package ${text}...`,
packageUndeployed: text => `Package ${text} undeployed...`,
consent: (url) => `Please, consent access to this app at: [Microsoft Online](${url}).`,
wrong_password: 'Sorry, wrong password. Please, try again.'
wrong_password: 'Sorry, wrong password. Please, try again.',
enter_authenticator_tenant: 'Enter the Authenticator Tenant (eg.: domain.onmicrosoft.com):',
enter_authenticator_authority_host_url: 'Enter the Authority Host URL (eg.: https://login.microsoftonline.com): ',
enter_authenticator_client_id: 'Enter the Client Id [Application Id](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredAppsPreview) GUID:',
enter_authenticator_client_secret: 'Enter the Client Secret:'
},
'pt-BR': {
show_video: 'Vou te mostrar um vídeo. Por favor, aguarde...',
Expand Down
4 changes: 2 additions & 2 deletions packages/azuredeployer.gbapp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@

'use strict';

const UrlJoin = require('url-join');
import { GBMinInstance, IGBCoreService, IGBPackage } from 'botlib';
import { Sequelize } from 'sequelize-typescript';

export class GBWhatsappPackage implements IGBPackage {
export class GBAzureDeployerPackage implements IGBPackage {

public sysPackages: IGBPackage[] = null;

public loadPackage(core: IGBCoreService, sequelize: Sequelize): void {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ export class AzureDeployerService extends GBService {
logger.info(`Deploying Bot Storage...`);
const administratorLogin = `sa${GBAdminService.getRndReadableIdentifier()}`;
const administratorPassword = GBAdminService.getRndPassword();
const storageServer = `${name}-storage-server`;
const storageServer = `${name.toLowerCase()}-storage-server`;
const storageName = `${name}-storage`;
await this.createStorageServer(
name,
Expand Down Expand Up @@ -311,6 +311,11 @@ export class AzureDeployerService extends GBService {
instance.nlpAppId = nlpAppId;

logger.info(`Deploying Bot...`);

// TODO: Default endpoint, will be updated when it runs in production.

instance.botEndpoint = 'http://localhost:4242';

instance = await this.deployBootBot(
instance,
name,
Expand Down
Loading

0 comments on commit 4718fe4

Please sign in to comment.