Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TS-Bindings] Complete support for user management #12576

Merged
merged 6 commits into from
Jan 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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