From 6b902fdaafed955e7ba733c26e53adc955ea448a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Peter=20Rouven=20M=C3=BCller?= Date: Wed, 26 Jan 2022 17:49:00 +0100 Subject: [PATCH] [TS-Bindings] Complete support for user management (#12576) * Add ts bindings for the uncovered new user management endpoints changelog_begin - [Typescript Bindings] All methods for user management have been added to the bindings (createUser, deleteUser, getUser, listUsers, listUserRights, grantUserRight, revokeUserRight) changelog_end * Fix list user rights method & add tests * Reduce diff * Add missing delete method * Update language-support/ts/daml-ledger/index.ts Co-authored-by: Stephen Compall Co-authored-by: Stephen Compall --- .../build-and-lint-test/src/__tests__/test.ts | 46 +++--- language-support/ts/daml-ledger/index.ts | 131 +++++++++++++++++- 2 files changed, 149 insertions(+), 28 deletions(-) diff --git a/language-support/ts/codegen/tests/ts/build-and-lint-test/src/__tests__/test.ts b/language-support/ts/codegen/tests/ts/build-and-lint-test/src/__tests__/test.ts index ca7a735688e0..bcbb34d91a0b 100644 --- a/language-support/ts/codegen/tests/ts/build-and-lint-test/src/__tests__/test.ts +++ b/language-support/ts/codegen/tests/ts/build-and-lint-test/src/__tests__/test.ts @@ -1,11 +1,11 @@ // Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { ChildProcess, spawn, spawnSync } from 'child_process'; +import { ChildProcess, spawn } from 'child_process'; import { promises as fs } from 'fs'; import waitOn from 'wait-on'; import { encode } from 'jwt-simple'; -import Ledger, { Event, Stream, PartyInfo } from '@daml/ledger'; +import Ledger, { Event, Stream, PartyInfo, UserRightHelper } from '@daml/ledger'; import { Int, emptyMap, Map } from '@daml/types'; import pEvent from 'p-event'; import _ from 'lodash'; @@ -35,15 +35,6 @@ const BOB_PARTY = 'Bob'; const BOB_TOKEN = computeToken(BOB_PARTY); const CHARLIE_PARTY = 'Charlie' const CHARLIE_TOKEN = computeToken(CHARLIE_PARTY) -const USERNAME = "nice.user" -const USER_DETAILS = { - "user": { - "id": USERNAME, - "primary_party": ALICE_PARTY, - }, - "rights": [{"can_act_as": {"party": ALICE_PARTY}}] -}; -const USER_TOKEN = computeUserToken(USERNAME); let sandboxPort: number | undefined = undefined; const SANDBOX_PORT_FILE = 'sandbox.port'; @@ -89,17 +80,6 @@ beforeAll(async () => { sandboxPort = parseInt(sandboxPortData); console.log('Sandbox listening on port ' + sandboxPort.toString()); - // TODO: Replace this with a call to the json api as soon as the CreateUser endpoint has been merged. - const grpcurlUserArgs = [ - "-plaintext", - "-d", - JSON.stringify(USER_DETAILS), - "localhost:" + sandboxPort.toString(), - "com.daml.ledger.api.v1.admin.UserManagementService/CreateUser", - ]; - spawnSync('grpcurl', grpcurlUserArgs, {"encoding": "utf8"}) - console.log('Created user') - jsonApiProcess = spawnJvm( getEnv('JSON_API'), ['--ledger-host', 'localhost', '--ledger-port', `${sandboxPort}`, @@ -609,9 +589,25 @@ test('party API', async () => { test('user API', async () => { - const ledger = new Ledger({token: USER_TOKEN, httpBaseUrl: httpBaseUrl()}); - const user = await ledger.getUser() - expect(user).toEqual({ userId: USER_DETAILS.user.id, primaryParty: USER_DETAILS.user.primary_party }); + const ledger = new Ledger({token: computeUserToken("participant_admin"), httpBaseUrl: httpBaseUrl()}); + + const participantAdminUser = await ledger.getUser() + expect(participantAdminUser.userId).toEqual("participant_admin"); + expect(await ledger.listUserRights()).toEqual([ UserRightHelper.participantAdmin ]) + + const niceUser = "nice.user" + const niceUserRights = [ UserRightHelper.canActAs(ALICE_PARTY) ] + await ledger.createUser(niceUser, niceUserRights, ALICE_PARTY) + + expect(await ledger.getUser(niceUser)).toEqual({ userId: niceUser, primaryParty: ALICE_PARTY }) + expect(await ledger.listUserRights(niceUser)).toEqual(niceUserRights) + expect(await ledger.grantUserRights(niceUser, [ UserRightHelper.participantAdmin ])).toEqual([ UserRightHelper.participantAdmin ]) + expect(await ledger.revokeUserRights(niceUser, [ UserRightHelper.participantAdmin, UserRightHelper.canActAs(ALICE_PARTY) ])).toEqual([ UserRightHelper.participantAdmin, UserRightHelper.canActAs(ALICE_PARTY) ]) + + expect((await ledger.listUsers()).map(it => it.userId)).toEqual([ "participant_admin", niceUser ]) + await ledger.deleteUser(niceUser) + expect((await ledger.listUsers()).map(it => it.userId)).toEqual([ "participant_admin" ]) + }); test('package API', async () => { diff --git a/language-support/ts/daml-ledger/index.ts b/language-support/ts/daml-ledger/index.ts index fcd3126fbd2a..d33c82ec0c92 100644 --- a/language-support/ts/daml-ledger/index.ts +++ b/language-support/ts/daml-ledger/index.ts @@ -5,7 +5,7 @@ import * as jtv from '@mojotech/json-type-validation'; import fetch from 'cross-fetch'; import { EventEmitter } from 'events'; import WebSocket from 'isomorphic-ws'; -import _ from 'lodash'; +import _, { isUndefined } from 'lodash'; /** * The result of a ``query`` against the ledger. @@ -51,6 +51,50 @@ const userDecoder: jtv.Decoder = primaryParty: jtv.optional(jtv.string()), }); +export type CanActAs = { + type: "CanActAs" + party: string +} + +export type CanReadAs = { + type: "CanReadAs" + party: string +} + +export type ParticipantAdmin = { + type: "ParticipantAdmin" +} + +export type UserRight = CanActAs | CanReadAs | ParticipantAdmin + +export class UserRightHelper { + static canActAs(party: string): UserRight { + return { type: "CanActAs", party: party } + } + + static canReadAs(party: string): UserRight { + return { type: "CanReadAs", party: party } + } + + static participantAdmin: UserRight = { + type: "ParticipantAdmin" + } +} + +const userRightDecoder: jtv.Decoder = + jtv.oneOf( + jtv.object({ + type: jtv.constant("CanActAs"), + party: jtv.string() + }), + jtv.object({ + type: jtv.constant("CanReadAs"), + party: jtv.string() + }), + jtv.object({ + type: jtv.constant("ParticipantAdmin") + })) + export type PackageId = string; const decode = (decoder: jtv.Decoder, data: unknown): R => { @@ -1393,14 +1437,95 @@ class Ledger { /** * Get the current user details obtained by the currently used JWT. * + * @param userId The user id + * * @returns User details * */ - async getUser(): Promise { - const json = await this.submit('v1/user', undefined, 'get'); + async getUser(userId?: string): Promise { + const json = isUndefined(userId) ? + await this.submit('v1/user', undefined, 'get') : + await this.submit('v1/user', { 'userId': userId }, 'post') return decode(userDecoder, json); } + /** + * Lists the users on the ledger + * + * @returns user list + * + */ + async listUsers(): Promise { + const json = await this.submit('v1/users', undefined, 'get'); + return decode(jtv.array(userDecoder), json); + } + + /** + * Lists the rights associated with the given user id + * + * @param userId, if empty then the user id will obtained by the currently used JWT. + * + * @returns list of user rights + */ + async listUserRights(userId?: string): Promise { + const json = isUndefined(userId) ? + await this.submit('v1/user/rights', undefined, 'get') : + await this.submit('v1/user/rights', { 'userId': userId }) + return decode(jtv.array(userRightDecoder), json); + } + + /** + * Grants rights to a user + * + * @param userId The user to which rights shall be granted + * + * @param rights The rights which shall be granted + * + * @returns The rights which actually were granted (if a right was already granted, then it will not be in the return list) + */ + async grantUserRights(userId: string, rights: UserRight[]): Promise { + const json = await this.submit('v1/user/rights/grant', { 'userId': userId, 'rights': rights }) + return decode(jtv.array(userRightDecoder), json); + } + + /** + * Revokes rights from a user + * + * @param userId The user from which rights shall be revoked + * + * @param rights The rights which shall be revoked + * + * @returns The rights which actually were revoked (if a right was already revoked, then it will not be in the return list) + */ + async revokeUserRights(userId: string, rights: UserRight[]): Promise { + const json = await this.submit('v1/user/rights/revoke', { 'userId': userId, 'rights': rights }) + return decode(jtv.array(userRightDecoder), json); + } + + /** + * Creates a user + * + * @param userId The user ID + * @param rights The initial rights the user should have + * @param primaryParty The primary party the user should have + * + */ + async createUser(userId: string, rights: UserRight[], primaryParty?: string): Promise { + await this.submit('v1/user/create', { 'userId': userId, 'rights': rights, 'primaryParty': primaryParty }) + } + + /** + * Deletes a user + * + * @param userId The user ID + * @param rights The initial rights the user should have + * @param primaryParty The primary party the user should have + * + */ + async deleteUser(userId: string): Promise { + await this.submit('v1/user/delete', { 'userId': userId }) + } + /** * Allocate a new party. *