add auth provider factory
add oidc cache and respect token lifetime simplify account retrieval
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import encoding from 'k6/encoding';
|
||||
import * as types from "../types";
|
||||
|
||||
export const headersDefault = ({credential}: { credential: types.Account | types.Token }): { [key: string]: string } => {
|
||||
export const headersDefault = ({credential}: { credential: types.Credential }): { [key: string]: string } => {
|
||||
const isOIDCGuard = (credential as types.Token).tokenType !== undefined;
|
||||
const authOIDC = credential as types.Token;
|
||||
const authBasic = credential as types.Account;
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as defaults from "../defaults";
|
||||
import * as types from "../types";
|
||||
|
||||
export const fileUpload = <RT extends ResponseType | undefined>(
|
||||
{credential, userName, asset}: { credential: types.Account | types.Token; userName: string; asset: types.Asset }
|
||||
{credential, userName, asset}: { credential: types.Credential; userName: string; asset: types.Asset }
|
||||
): RefinedResponse<RT> => {
|
||||
return http.put(
|
||||
`${defaults.OC_HOST}/remote.php/dav/files/${userName}/${asset.fileName}`,
|
||||
@@ -18,7 +18,7 @@ export const fileUpload = <RT extends ResponseType | undefined>(
|
||||
}
|
||||
|
||||
export const fileDownload = <RT extends ResponseType | undefined>(
|
||||
{credential, userName, fileName}: { credential: types.Account | types.Token; userName: string; fileName: string }
|
||||
{credential, userName, fileName}: { credential: types.Credential; userName: string; fileName: string }
|
||||
): RefinedResponse<RT> => {
|
||||
return http.get(
|
||||
`${defaults.OC_HOST}/remote.php/dav/files/${userName}/${fileName}`,
|
||||
@@ -31,7 +31,7 @@ export const fileDownload = <RT extends ResponseType | undefined>(
|
||||
}
|
||||
|
||||
export const fileDelete = <RT extends ResponseType | undefined>(
|
||||
{credential, userName, fileName}: { credential: types.Account | types.Token; userName: string; fileName: string }
|
||||
{credential, userName, fileName}: { credential: types.Credential; userName: string; fileName: string }
|
||||
): RefinedResponse<RT> => {
|
||||
return http.del(
|
||||
`${defaults.OC_HOST}/remote.php/dav/files/${userName}/${fileName}`,
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as defaults from "../defaults";
|
||||
import * as types from "../types";
|
||||
|
||||
export const userInfo = <RT extends ResponseType | undefined>(
|
||||
{credential, userName}: { credential: types.Account | types.Token; userName: string; }
|
||||
{credential, userName}: { credential: types.Credential; userName: string; }
|
||||
): RefinedResponse<RT> => {
|
||||
return http.get(
|
||||
`${defaults.OC_HOST}/ocs/v1.php/cloud/users/${userName}`,
|
||||
|
||||
+142
-66
@@ -5,83 +5,159 @@ import * as types from "./types";
|
||||
import {fail} from 'k6';
|
||||
import {get} from 'lodash'
|
||||
|
||||
export const oidc = (account: types.Account): types.Token => {
|
||||
const redirectUri = `${defaults.OC_OIDC_HOST}/oidc-callback.html`;
|
||||
|
||||
const logonUri = `${defaults.OC_OIDC_HOST}/signin/v1/identifier/_/logon`;
|
||||
const logonResponse = http.post(
|
||||
logonUri,
|
||||
JSON.stringify(
|
||||
{
|
||||
params: [account.login, account.password, '1'],
|
||||
hello: {
|
||||
scope: 'openid profile email',
|
||||
client_id: 'phoenix',
|
||||
redirect_uri: redirectUri,
|
||||
flow: 'oidc'
|
||||
},
|
||||
'state': 'vp42cf'
|
||||
},
|
||||
),
|
||||
{
|
||||
headers: {
|
||||
'Kopano-Konnect-XSRF': '1',
|
||||
Referer: defaults.OC_OIDC_HOST,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
);
|
||||
const authorizeURI = get(logonResponse.json(), 'hello.continue_uri');
|
||||
export default class Factory {
|
||||
private provider!: types.AuthProvider;
|
||||
public account!: types.Account;
|
||||
|
||||
if (logonResponse.status != 200 || !authorizeURI) {
|
||||
fail(logonUri);
|
||||
constructor(account: types.Account) {
|
||||
this.account = account;
|
||||
|
||||
if (defaults.OC_OIDC) {
|
||||
this.provider = new OIDCProvider(account);
|
||||
}
|
||||
|
||||
if (!defaults.OC_OIDC) {
|
||||
this.provider = new AccountProvider(account);
|
||||
}
|
||||
}
|
||||
|
||||
const authorizeUri = `${authorizeURI}?${
|
||||
queryString.stringify(
|
||||
public get credential(): types.Credential {
|
||||
return this.provider.credential
|
||||
}
|
||||
}
|
||||
|
||||
class AccountProvider implements types.AuthProvider {
|
||||
private account: types.Account;
|
||||
|
||||
constructor(account: types.Account) {
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
public get credential(): types.Account {
|
||||
return this.account;
|
||||
}
|
||||
}
|
||||
|
||||
class OIDCProvider implements types.AuthProvider {
|
||||
private account: types.Account;
|
||||
private redirectUri = `${defaults.OC_OIDC_HOST}/oidc-callback.html`;
|
||||
private logonUri = `${defaults.OC_OIDC_HOST}/signin/v1/identifier/_/logon`;
|
||||
private tokenUrl = `${defaults.OC_OIDC_HOST}/konnect/v1/token`;
|
||||
private cache!: {
|
||||
validTo: Date;
|
||||
token: types.Token;
|
||||
}
|
||||
|
||||
constructor(account: types.Account) {
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
public get credential(): types.Token {
|
||||
if (!this.cache || this.cache.validTo <= new Date()) {
|
||||
const continueURI = this.getContinueURI();
|
||||
const code = this.getCode(continueURI);
|
||||
const token = this.getToken(code);
|
||||
|
||||
this.cache = {
|
||||
validTo: ((): Date => {
|
||||
const offset = 5;
|
||||
const d = new Date();
|
||||
|
||||
d.setSeconds(d.getSeconds() + token.expiresIn - offset)
|
||||
|
||||
return d
|
||||
})(),
|
||||
token,
|
||||
}
|
||||
}
|
||||
|
||||
return this.cache.token;
|
||||
}
|
||||
|
||||
private getContinueURI(): string {
|
||||
const logonResponse = http.post(
|
||||
this.logonUri,
|
||||
JSON.stringify(
|
||||
{
|
||||
params: [this.account.login, this.account.password, '1'],
|
||||
hello: {
|
||||
scope: 'openid profile email',
|
||||
client_id: 'phoenix',
|
||||
redirect_uri: this.redirectUri,
|
||||
flow: 'oidc'
|
||||
},
|
||||
'state': 'vp42cf'
|
||||
},
|
||||
),
|
||||
{
|
||||
client_id: 'phoenix',
|
||||
prompt: 'none',
|
||||
redirect_uri: redirectUri,
|
||||
response_mode: 'query',
|
||||
response_type: 'code',
|
||||
scope: 'openid profile email',
|
||||
headers: {
|
||||
'Kopano-Konnect-XSRF': '1',
|
||||
Referer: defaults.OC_OIDC_HOST,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
);
|
||||
const continueURI = get(logonResponse.json(), 'hello.continue_uri');
|
||||
|
||||
if (logonResponse.status != 200 || !continueURI) {
|
||||
fail(this.logonUri);
|
||||
}
|
||||
|
||||
return continueURI;
|
||||
}
|
||||
|
||||
private getCode(continueURI: string): string {
|
||||
const authorizeUri = `${continueURI}?${
|
||||
queryString.stringify(
|
||||
{
|
||||
client_id: 'phoenix',
|
||||
prompt: 'none',
|
||||
redirect_uri: this.redirectUri,
|
||||
response_mode: 'query',
|
||||
response_type: 'code',
|
||||
scope: 'openid profile email',
|
||||
},
|
||||
)
|
||||
}`;
|
||||
const authorizeResponse = http.get(
|
||||
authorizeUri,
|
||||
{
|
||||
redirects: 0,
|
||||
},
|
||||
)
|
||||
}`;
|
||||
const authorizeResponse = http.get(
|
||||
authorizeUri,
|
||||
{
|
||||
redirects: 0,
|
||||
},
|
||||
)
|
||||
const authCode = get(queryString.parseUrl(authorizeResponse.headers.Location), 'query.code')
|
||||
|
||||
if (authorizeResponse.status != 302 || !authCode) {
|
||||
fail(authorizeURI);
|
||||
}
|
||||
const code = get(queryString.parseUrl(authorizeResponse.headers.Location), 'query.code')
|
||||
|
||||
const tokenUrl = `${defaults.OC_OIDC_HOST}/konnect/v1/token`;
|
||||
const tokenResponse = http.post(
|
||||
tokenUrl,
|
||||
{
|
||||
client_id: 'phoenix',
|
||||
code: authCode,
|
||||
redirect_uri: redirectUri,
|
||||
grant_type: 'authorization_code'
|
||||
if (authorizeResponse.status != 302 || !code) {
|
||||
fail(continueURI);
|
||||
}
|
||||
)
|
||||
|
||||
const token = {
|
||||
accessToken: get(tokenResponse.json(), 'access_token'),
|
||||
tokenType: get(tokenResponse.json(), 'token_type'),
|
||||
idToken: get(tokenResponse.json(), 'id_token'),
|
||||
expiresIn: get(tokenResponse.json(), 'expires_in'),
|
||||
return code
|
||||
}
|
||||
|
||||
if (tokenResponse.status != 200 || !token.accessToken || !token.tokenType || !token.idToken || !token.expiresIn) {
|
||||
fail(authorizeURI);
|
||||
}
|
||||
private getToken(code: string): types.Token {
|
||||
const tokenResponse = http.post(
|
||||
this.tokenUrl,
|
||||
{
|
||||
client_id: 'phoenix',
|
||||
code,
|
||||
redirect_uri: this.redirectUri,
|
||||
grant_type: 'authorization_code'
|
||||
}
|
||||
)
|
||||
|
||||
return token
|
||||
}
|
||||
const token = {
|
||||
accessToken: get(tokenResponse.json(), 'access_token'),
|
||||
tokenType: get(tokenResponse.json(), 'token_type'),
|
||||
idToken: get(tokenResponse.json(), 'id_token'),
|
||||
expiresIn: get(tokenResponse.json(), 'expires_in'),
|
||||
}
|
||||
|
||||
if (tokenResponse.status != 200 || !token.accessToken || !token.tokenType || !token.idToken || !token.expiresIn) {
|
||||
fail(this.tokenUrl);
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,16 +11,32 @@ export const OC_TEST_FILE = {
|
||||
fileName: ocTestFile,
|
||||
bytes: open(ocTestFile, 'b'),
|
||||
}
|
||||
export const k6OptionsDefault: Options = {
|
||||
export const K6_OPTION_DEFAULTS: Options = {
|
||||
insecureSkipTLSVerify: true,
|
||||
};
|
||||
export const knownAccounts: { [key: string]: types.Account; } = {
|
||||
einstein: {
|
||||
login: 'einstein',
|
||||
password: 'relativity',
|
||||
},
|
||||
richard: {
|
||||
login: 'richard',
|
||||
password: 'superfluidity',
|
||||
},
|
||||
}
|
||||
|
||||
export class ACCOUNTS {
|
||||
public static readonly EINSTEIN = 'einstein';
|
||||
public static readonly RICHARD = 'richard';
|
||||
private static readonly list: { [key: string]: types.Account; } = {
|
||||
einstein: {
|
||||
login: 'einstein',
|
||||
password: 'relativity',
|
||||
},
|
||||
richard: {
|
||||
login: 'richard',
|
||||
password: 'superfluidity',
|
||||
},
|
||||
}
|
||||
|
||||
public static for(key: string): types.Account {
|
||||
if (OC_LOGIN && OC_PASSWORD) {
|
||||
return {
|
||||
login: OC_LOGIN,
|
||||
password: OC_PASSWORD,
|
||||
}
|
||||
}
|
||||
|
||||
return this.list[key];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ export const fileUpload = () => {
|
||||
const fileUploadTrend = new Trend('occ_file_upload_trend', true);
|
||||
const fileUploadErrorRate = new Gauge('occ_file_upload_error_rate');
|
||||
|
||||
return ({credential, userName, asset}: { credential: types.Account | types.Token; userName: string; asset: types.Asset }): string => {
|
||||
return ({credential, userName, asset}: { credential: types.Credential; userName: string; asset: types.Asset }): string => {
|
||||
const fileName = `upload-${userName}-${__VU}-${__ITER}.${utils.extension(asset.fileName)}`;
|
||||
const uploadResponse = api.dav.fileUpload({
|
||||
credential: credential as any,
|
||||
@@ -33,7 +33,7 @@ export const fileDelete = () => {
|
||||
const fileDeleteTrend = new Trend('occ_file_delete_trend', true);
|
||||
const fileDeleteErrorRate = new Gauge('occ_file_delete_error_rate');
|
||||
|
||||
return ({credential, userName, fileName}: { credential: types.Account | types.Token, userName: string; fileName: string }) => {
|
||||
return ({credential, userName, fileName}: { credential: types.Credential, userName: string; fileName: string }) => {
|
||||
const deleteResponse = api.dav.fileDelete({
|
||||
credential: credential as any,
|
||||
fileName,
|
||||
@@ -52,7 +52,7 @@ export const fileDownload = () => {
|
||||
const fileDownloadTrend = new Trend('occ_file_download_trend', true);
|
||||
const fileDownloadErrorRate = new Gauge('occ_file_download_error_rate');
|
||||
|
||||
return ({credential, userName, fileName}: { credential: types.Account | types.Token, userName: string; fileName: string }): bytes => {
|
||||
return ({credential, userName, fileName}: { credential: types.Credential, userName: string; fileName: string }): bytes => {
|
||||
const downloadResponse = api.dav.fileDownload({
|
||||
credential: credential as any,
|
||||
fileName,
|
||||
|
||||
@@ -9,10 +9,16 @@ export interface Token {
|
||||
accessToken: string;
|
||||
tokenType: string;
|
||||
idToken: string;
|
||||
expiresIn: string;
|
||||
expiresIn: number;
|
||||
}
|
||||
|
||||
export interface Account {
|
||||
login: string
|
||||
password: string
|
||||
}
|
||||
|
||||
export type Credential = Token | Account
|
||||
|
||||
export interface AuthProvider {
|
||||
credential: Credential
|
||||
}
|
||||
@@ -1,6 +1,3 @@
|
||||
import * as types from "./types";
|
||||
import * as defaults from "./defaults";
|
||||
|
||||
export const randomString = (): string => {
|
||||
return Math.random().toString(36).slice(2)
|
||||
}
|
||||
@@ -9,14 +6,3 @@ export const extension = (p: string): string | undefined => {
|
||||
return (p.split('/').pop())!.split('.').pop()
|
||||
}
|
||||
|
||||
export const getAccount = (key: string): types.Account => {
|
||||
if (defaults.OC_LOGIN && defaults.OC_PASSWORD) {
|
||||
return {
|
||||
login: defaults.OC_LOGIN,
|
||||
password: defaults.OC_PASSWORD,
|
||||
}
|
||||
}
|
||||
|
||||
return defaults.knownAccounts[key];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,35 +1,24 @@
|
||||
import {defaults, playbook} from '../../lib'
|
||||
import {Options} from 'k6/options';
|
||||
import {sleep} from "k6";
|
||||
import * as auth from "../../lib/auth";
|
||||
import * as types from "../../lib/types";
|
||||
import * as utils from "../../lib/utils";
|
||||
|
||||
interface dataI {
|
||||
credential: types.Account | types.Token;
|
||||
}
|
||||
import auth from "../../lib/auth";
|
||||
|
||||
export const options: Options = {
|
||||
...defaults.k6OptionsDefault,
|
||||
...defaults.K6_OPTION_DEFAULTS,
|
||||
iterations: 1,
|
||||
vus: 1,
|
||||
};
|
||||
const account = utils.getAccount('einstein');
|
||||
const authFactory = new auth(defaults.ACCOUNTS.for(defaults.ACCOUNTS.EINSTEIN));
|
||||
const playbooks = {
|
||||
fileUpload: playbook.dav.fileUpload(),
|
||||
fileDownload: playbook.dav.fileDownload(),
|
||||
fileDelete: playbook.dav.fileDelete(),
|
||||
}
|
||||
export const setup = (): dataI => {
|
||||
return {
|
||||
credential: defaults.OC_OIDC ? auth.oidc(account) : account,
|
||||
}
|
||||
}
|
||||
export default (data: dataI) => {
|
||||
const credential = data.credential;
|
||||
const userName = account.login;
|
||||
|
||||
export default () => {
|
||||
const {login: userName} = authFactory.account;
|
||||
const fileName = playbooks.fileUpload({
|
||||
credential,
|
||||
credential: authFactory.credential,
|
||||
userName,
|
||||
asset: defaults.OC_TEST_FILE
|
||||
});
|
||||
@@ -37,7 +26,7 @@ export default (data: dataI) => {
|
||||
sleep(1)
|
||||
|
||||
playbooks.fileDownload({
|
||||
credential,
|
||||
credential: authFactory.credential,
|
||||
userName,
|
||||
fileName,
|
||||
});
|
||||
@@ -45,7 +34,7 @@ export default (data: dataI) => {
|
||||
sleep(1)
|
||||
|
||||
playbooks.fileDelete({
|
||||
credential,
|
||||
credential: authFactory.credential,
|
||||
userName,
|
||||
fileName,
|
||||
});
|
||||
|
||||
@@ -1,34 +1,22 @@
|
||||
import {defaults, playbook} from '../../lib'
|
||||
import {Options} from 'k6/options';
|
||||
import {sleep} from "k6";
|
||||
import * as auth from "../../lib/auth";
|
||||
import * as types from "../../lib/types";
|
||||
import * as utils from "../../lib/utils";
|
||||
|
||||
interface dataI {
|
||||
credential: types.Account | types.Token;
|
||||
}
|
||||
import auth from "../../lib/auth";
|
||||
|
||||
export const options: Options = {
|
||||
...defaults.k6OptionsDefault,
|
||||
...defaults.K6_OPTION_DEFAULTS,
|
||||
iterations: 1,
|
||||
vus: 1,
|
||||
};
|
||||
const account = utils.getAccount('einstein');
|
||||
const authFactory = new auth(defaults.ACCOUNTS.for(defaults.ACCOUNTS.EINSTEIN));
|
||||
const playbooks = {
|
||||
fileUpload: playbook.dav.fileUpload(),
|
||||
fileDelete: playbook.dav.fileDelete(),
|
||||
}
|
||||
export const setup = (): dataI => {
|
||||
return {
|
||||
credential: defaults.OC_OIDC ? auth.oidc(account) : account,
|
||||
}
|
||||
}
|
||||
export default (data: dataI) => {
|
||||
const credential = data.credential;
|
||||
const userName = account.login;
|
||||
export default () => {
|
||||
const {login: userName} = authFactory.account;
|
||||
const fileName = playbooks.fileUpload({
|
||||
credential,
|
||||
credential: authFactory.credential,
|
||||
userName,
|
||||
asset: defaults.OC_TEST_FILE
|
||||
});
|
||||
@@ -36,7 +24,7 @@ export default (data: dataI) => {
|
||||
sleep(1)
|
||||
|
||||
playbooks.fileDelete({
|
||||
credential,
|
||||
credential: authFactory.credential,
|
||||
userName,
|
||||
fileName,
|
||||
});
|
||||
|
||||
@@ -6,5 +6,4 @@ export const options: Options = {
|
||||
iterations: 200,
|
||||
vus: 50,
|
||||
};
|
||||
export const {setup} = uploadFilesBenchmark;
|
||||
export default uploadFilesBenchmark.default;
|
||||
Reference in New Issue
Block a user