Skip to content
Snippets Groups Projects
Commit 1e94dc62 authored by Gomez Goiri, Aitor's avatar Gomez Goiri, Aitor
Browse files

Allowing to define a custom affiliation attribute and adding new SDK client

The SDK client differs in creating an actual "hf.Affiliation" too  (with the deployment restrictions specified).
parent 9b7ff515
No related branches found
No related tags found
No related merge requests found
# Create sample users for organizations # Create sample users for organizations
## Configuration
Edit admin users and users to be created in _users.json_.
You can provide connection details and admin passwords in _.env_ (you can use _sample.env_ as a template).
### Create/update users with the API
The script _config_users.js_ will take care of creating the necessary users in all the organizations. The script _config_users.js_ will take care of creating the necessary users in all the organizations.
```bash ```bash
node config_users.js npm run users
# or node config-users.js api
``` ```
## Configuration ### Create/update users with the SDK
Edit admin users and users to be created in _users.json_. We must use this version to create users with a customized affiliation.
You can provide connection details and admin passwords in _.env_ (you can use _sample.env_ as a template).
```bash
npm run users-sdk
# or node config-users.js sdk
```
**Note**: By default Fabric only defines [3 affiliations](https://hyperledger-fabric-ca.readthedocs.io/en/release-1.4/deployguide/ca-config.html#affiliations) and admins belong to _org1.department1_ (which means that they can only create subaffiliations).
Since I didn't learn how to modify them in Minifab, new affiliations are prefixed by this prefix making it ugly and confusing :(
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
require("dotenv").config() require("dotenv").config()
const ApiClient = require("@traceblock/api-client") const ApiClient = require("@traceblock/api-client")
const SDKHelper = require("./sdk")
const usersByOrg = require("./users") const usersByOrg = require("./users")
...@@ -11,18 +12,26 @@ const readPassword = (user, org) => ...@@ -11,18 +12,26 @@ const readPassword = (user, org) =>
"secret" "secret"
const modifyUser = async ( const modifyUser = async (
apiClient, client,
username, username,
password, password,
organization, organization,
role role,
email,
affiliation
) => { ) => {
const oldCert = await apiClient.getUser(username) const oldCert = await client.getUser(username, organization)
const modUser = await apiClient.modifyUser( oldCert.attrs = [
...oldCert.attrs,
{ name: "affiliation", value: affiliation, ecert: true }
]
const modUser = await client.modifyUser(
username, username,
password, password,
organization, organization,
role, role,
email,
oldCert oldCert
) )
...@@ -34,50 +43,107 @@ const modifyUser = async ( ...@@ -34,50 +43,107 @@ const modifyUser = async (
} }
const createOrModifyUserIfNeeded = async ( const createOrModifyUserIfNeeded = async (
apiClient, client,
username, username,
password, password,
organization, organization,
role role,
affiliation
) => { ) => {
const email = `${username}@${organization}`
try { try {
// Await is needed here to force the catch clausule if something goes wrong // Await is needed here to force the catch clausule if something goes wrong
return await apiClient.createUser( return await client.createUser(
username, username,
password, password,
organization, organization,
role, role,
`${username}@${organization}` email,
"en",
[{ name: "affiliation", value: affiliation, ecert: true }]
) )
} catch (err) { } catch (err) {
return modifyUser(apiClient, username, password, organization, role) return modifyUser(
client,
username,
password,
organization,
role,
email,
affiliation
)
} }
} }
const createUsers = async () => { const printAffiliations = async (sdkClient, organization) => {
const apiClient = new ApiClient( console.log(`\nAffiliations for ${organization}`)
const existingAffiliations = await sdkClient.getAffiliations(organization)
existingAffiliations.forEach(({ name, affiliations: affs = [] }) =>
console.log(`\t${name}: [ ${affs.map(a => a.name).join(", ")} ] `)
)
}
const createAffiliations = async (
sdkClient,
organization,
affiliations = []
) => {
if (affiliations.length > 0) {
for (let affiliation of affiliations) {
try {
await sdkClient.addAffiliation(organization, affiliation)
} catch (err) {
err.errors.forEach(({ message }) => console.error("Error:", message))
}
}
await printAffiliations(sdkClient, organization)
}
}
const createUsers = async (useSdk = false) => {
let client = new ApiClient(
process.env.TRACEBLOCK_API, process.env.TRACEBLOCK_API,
process.env.TRACEBLOCK_NETWORK, process.env.TRACEBLOCK_NETWORK,
process.env.TRACEBLOCK_CHANNEL, process.env.TRACEBLOCK_CHANNEL,
process.env.TRACEBLOCK_CHAINCODE process.env.TRACEBLOCK_CHAINCODE
) )
if (useSdk) {
// CA nodes must be accesible.
// If this script is not run from a container inside the network, expose the ports.
// Append "--expose-endpoints true" to deployment/network/up.sh
client = new SDKHelper(process.env.CONNECTION_CONFIG)
}
for (let el of Object.entries(usersByOrg)) { for (let el of Object.entries(usersByOrg)) {
const [org, values] = el const [org, values] = el
const admin = values.find(v => v.role === "admin") const admin = values.find(v => v.role === "admin")
await apiClient.signIn(admin.user, readPassword(admin.user, org), org) await client.signIn(admin.user, readPassword(admin.user, org), org)
if (useSdk) {
// The API does not include an option to create new affiliations
await createAffiliations(
client,
org,
values.filter(u => !!u.affiliation).map(u => u.affiliation)
)
}
const prom = values const prom = values
//.filter(v => v.role !== "admin") //.filter(v => v.role !== "admin")
// Admin roles also need to be configured // Admin roles also need to be configured
.map(u => .map(u =>
createOrModifyUserIfNeeded( createOrModifyUserIfNeeded(
apiClient, client,
u.user, u.user,
readPassword(u.user, org), readPassword(u.user, org),
org, org,
u.role u.role,
u.affiliation
) )
) )
...@@ -87,4 +153,5 @@ const createUsers = async () => { ...@@ -87,4 +153,5 @@ const createUsers = async () => {
console.log("Users created") console.log("Users created")
} }
createUsers() const [clientType = ""] = process.argv.slice(2)
createUsers(clientType.toLowerCase() === "sdk")
This diff is collapsed.
...@@ -3,10 +3,13 @@ ...@@ -3,10 +3,13 @@
"version": "0.0.1", "version": "0.0.1",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@traceblock/api-client": "^0.2.9", "@traceblock/api-client": "^0.2.10",
"dotenv": "^10.0.0" "dotenv": "^16.0.0",
"fabric-ca-client": "^2.2.11",
"fabric-network": "^2.2.11"
}, },
"scripts": { "scripts": {
"users": "node create-users.js" "users": "node create-users.js api",
"users-sdk": "node create-users.js sdk"
} }
} }
...@@ -10,3 +10,6 @@ PASSWD-ADMIN-SIDENOR.COM=password ...@@ -10,3 +10,6 @@ PASSWD-ADMIN-SIDENOR.COM=password
PASSWD-ADMIN-CEMENT-COMPANY1.COM=password PASSWD-ADMIN-CEMENT-COMPANY1.COM=password
PASSWD-ADMIN-CEMENT-COMPANY2.COM=password PASSWD-ADMIN-CEMENT-COMPANY2.COM=password
PASSWD-ADMIN-PUBLIC-ADMINISTRATION.COM=password PASSWD-ADMIN-PUBLIC-ADMINISTRATION.COM=password
# Necessary for create-users-with-sdk.js
CONNECTION_CONFIG=../deployment/network/vars/profiles/hypercog_connection_for_nodesdk.json
\ No newline at end of file
// COPYRIGHT: FUNDACIÓN TECNALIA RESEARCH & INNOVATION, 2022.
require("dotenv").config()
const fs = require("fs")
const { Wallets } = require("fabric-network")
const FabricCAServices = require("fabric-ca-client")
const loadProfile = connectionConfigPath => {
if (fs.existsSync(connectionConfigPath)) {
return JSON.parse(fs.readFileSync(connectionConfigPath, "utf8"))
}
throw new Error(`no such file or directory: ${connectionConfigPath}`)
}
const updateAttrs = (newAttrs = [], oldAttrs = []) => [
...oldAttrs
// Reserved prefix, Registrar is not authorized to register it
.filter(attr => !attr.name.startsWith("hf."))
// Remove old values of the new attrs to be added
.filter(attr => !newAttrs.map(a => a.name).find(e => e === attr.name)),
...newAttrs
]
const createAttrs = (organization, role, affiliation, email, lang = "es") => [
// Attrs used in https://git.code.tecnalia.com/ledgerbuilder/sdk/-/blob/develop/core/stub/stub.go
{ name: "org", value: organization, ecert: true },
{ name: "role", value: role, ecert: true }, // Also used in frontend
{ name: "email", value: email },
{ name: "lang", value: lang, ecert: true }
]
// By default Fabric only defines these 3 affiliations:
// https://hyperledger-fabric-ca.readthedocs.io/en/release-1.4/deployguide/ca-config.html#affiliations
// Since I didn't learn how to modify them in Minifab,
// new affiliations must be prefixed by one of those making it ugly and confusing :(
const toValidHfAffiliation = affiliation =>
affiliation ? "org1.department1." + affiliation : "org1.department1"
class SDKHelper {
constructor(connectionProfilePath) {
this.connectionProfile = loadProfile(connectionProfilePath)
this.caClient = null
this.wallet = {}
}
async _getWallet(org) {
// setup the wallet to hold the credentials of the application user
if (!this.wallet[org]) {
this.wallet[org] = await Wallets.newInMemoryWallet()
}
return this.wallet[org]
}
_getMspId(org) {
const orgProfile = this.connectionProfile.organizations[org]
if (!orgProfile) {
throw new Error(`no such organization in the connection profile: ${org}`)
}
return orgProfile.mspid
}
_setCAClient(org) {
const orgProfile = this.connectionProfile.organizations[org]
if (!orgProfile) {
throw new Error(`no such organization in the connection profile: ${org}`)
}
const caHostName = orgProfile.certificateAuthorities[0]
const caInfo = this.connectionProfile.certificateAuthorities[caHostName]
const caTLSCACerts = caInfo.tlsCACerts.pem
this.caClient = new FabricCAServices(
caInfo.url,
{ trustedRoots: caTLSCACerts, verify: false },
caInfo.caName
)
}
async _getAdminUser(organization) {
const wallet = await this._getWallet(organization)
const adminIdentity = await wallet.get("admin")
if (!adminIdentity) {
throw new Error(
"An identity for the admin user does not exist in the wallet"
)
}
const provider = wallet
.getProviderRegistry()
.getProvider(adminIdentity.type)
return provider.getUserContext(adminIdentity, "admin")
}
async _enrollUser(userId, password, org) {
const mspId = this._getMspId(org)
const enrollment = await this.caClient.enroll({
enrollmentID: userId,
enrollmentSecret: password
})
// x509Identity
return {
credentials: {
certificate: enrollment.certificate,
privateKey: enrollment.key.toBytes()
},
mspId: mspId,
type: "X.509"
}
}
async signIn(userId, password, org) {
const wallet = await this._getWallet(org)
// Check to see if we've already enrolled the admin user.
const identity = await wallet.get(userId)
if (identity) {
console.log(
`Signing: An identity for the user ${userId} already exists in the wallet`
)
return
}
this._setCAClient(org)
const x509Identity = await this._enrollUser(userId, password, org)
await wallet.put(userId, x509Identity)
}
async createUser(
userId,
password,
organization,
role,
email,
language,
extraAttrs = []
) {
const adminUser = await this._getAdminUser(organization)
// returns secret
await this.caClient.register(
{
enrollmentID: userId,
enrollmentSecret: password,
role: "client", // "hf.Type" in the certificate
affiliation: toValidHfAffiliation(affiliation),
maxEnrollments: -1, // unlimited
attrs: updateAttrs(
createAttrs(organization, role, email, language),
extraAttrs
)
},
adminUser
)
// Register the user, enroll the user, and import the new identity into the wallet.
// if affiliation is specified by client, the affiliation value must be configured in CA
const secret = await this._register(organization)
await this._enrollUser(userId, secret, organization)
}
/**
* Modifies the information of a user.
* Note that only a user with an `admin` role can do this.
*
* @param {string} username Username of the user.
* @param {string} password New password of the user.
* @param {string} organization New organization of the user.
* @param {string} role New role for the user.
* @param {string} email E-mail of the new user.
* @param {object} [oldCert] X.509 certificate fields
* @param {Array.<AttributeX509>} [oldCert.attrs=[]] Attributes of the certificate.
*/
async modifyUser(userId, password, organization, role, email, oldCert = {}) {
const wallet = await this._getWallet(organization)
// Must use an admin to register a new user
const adminIdentity = await wallet.get("admin")
if (!adminIdentity) {
console.log("An identity for the admin user does not exist in the wallet")
console.log("Enroll the admin user before retrying")
return
}
const provider = wallet
.getProviderRegistry()
.getProvider(adminIdentity.type)
const adminUser = await provider.getUserContext(adminIdentity, "admin")
const identityService = await this.caClient.newIdentityService()
const affiliationAttr = oldCert.attrs.find(
attr => attr.name === "affiliation"
)
const affiliation = affiliationAttr
? toValidHfAffiliation(affiliationAttr.value)
: undefined
return identityService.update(
userId,
{
enrollmentID: userId,
secret: password,
affiliation,
role: "client", // "hf.Type" in the certificate
attrs: updateAttrs(
createAttrs(organization, role, email),
oldCert.attrs
)
},
adminUser
)
}
// Order important since "organization" param is not in API client
async getUser(userId, organization) {
const identityService = await this.caClient.newIdentityService()
const { success, result } = await identityService.getOne(
userId,
await this._getAdminUser(organization)
)
return success ? result : null
}
async getAffiliations(organization) {
const affiliationService = await this.caClient.newAffiliationService()
const affResp = await affiliationService.getAll(
await this._getAdminUser(organization)
)
if (!affResp.success) {
console.error(affResp.errors)
return []
}
return affResp.result.affiliations
}
async updateAffiliation(organization, oldAffiliation, affiliation) {
const affiliationService = await this.caClient.newAffiliationService()
await affiliationService.update(
oldAffiliation,
// Force: If any of the parent affiliations do not exist, create all parent affiliations also.
{ name: toValidHfAffiliation(affiliation), force: true },
await this._getAdminUser(organization)
)
}
async addAffiliation(organization, affiliation) {
const affiliationService = await this.caClient.newAffiliationService()
await affiliationService.create(
// Force: If any of the parent affiliations do not exist, create all parent affiliations also.
{ name: toValidHfAffiliation(affiliation), force: true },
await this._getAdminUser(organization)
)
}
}
module.exports = SDKHelper
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment