Skip to content

Commit

Permalink
[TS-Bindings] Complete support for user management (#12576)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

Co-authored-by: Stephen Compall <[email protected]>
  • Loading branch information
realvictorprm and S11001001 authored Jan 26, 2022
1 parent f04dcfa commit 6b902fd
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 28 deletions.
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -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}`,
Expand Down Expand Up @@ -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 () => {
Expand Down
131 changes: 128 additions & 3 deletions language-support/ts/daml-ledger/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -51,6 +51,50 @@ const userDecoder: jtv.Decoder<User> =
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<UserRight> =
jtv.oneOf<UserRight>(
jtv.object<CanActAs>({
type: jtv.constant("CanActAs"),
party: jtv.string()
}),
jtv.object<CanReadAs>({
type: jtv.constant("CanReadAs"),
party: jtv.string()
}),
jtv.object<ParticipantAdmin>({
type: jtv.constant("ParticipantAdmin")
}))

export type PackageId = string;

const decode = <R>(decoder: jtv.Decoder<R>, data: unknown): R => {
Expand Down Expand Up @@ -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<User> {
const json = await this.submit('v1/user', undefined, 'get');
async getUser(userId?: string): Promise<User> {
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<User[]> {
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<UserRight[]> {
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<UserRight[]> {
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<UserRight[]> {
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<void> {
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<void> {
await this.submit('v1/user/delete', { 'userId': userId })
}

/**
* Allocate a new party.
*
Expand Down

0 comments on commit 6b902fd

Please sign in to comment.