Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Introduction

...

  • Send the OTP to the user’s email/phone to validate the user. (OTP templates for user account deletion verification)Insert the entry for all the type as false in sunbird.user_deletion_status.

Sync

  • Delete login credentials and sessions from Keycloak.

  • Update the sunbird.user table. Set the following fields as empty: (redis data for the following fields will become empty for that user)

    • firstName

    • lastName

    • email

    • dob

    • phone

    • maskedEmail

    • maskedPhone

    • prevUsedEmail

    • prevUsedPhone

    • recoveryEmail

    • recoveryPhone

  • Update the status from ACTIVE to DELETED.

  • Remove the user entry from sunbird.user_lookup table.

  • Remove the SSO user entry from sunbird.user_external_identity .Update the user entry in sunbird.user_organisation (

  • Send the AUDIT telemetry event after successful/failure update/deletion of the above tables.

  • Trigger the delete user kafka event {{env_name}}.delete.user

User Deletion Flink Job

  • Verify the sync steps is successfully completed else perform the pending actions.

  • Update the user entry in sunbird.user_organisation (async) - May not be required.

    • isdeleted - True

    • orgleftdate - system date

  • Update the user’s name in nodeBB as Deleted User to display in discussion forum.

  • If a group admin/owner deletes his account, no action required if there is another group admin.If not, another user (random) can be assigned as the default group owner

  • Insert the entry in sunbird.user_deletion_status table for each type.

  • Sync the user deletion status in user index in below format:

    Code Block
    "userDeletionStatus" : {
     	"userLookUp": false,
     	"userExtIdnt": true,
     	"keycloak": false
     	"user": true,
        "discussionForum": true,
     	"userOwnershipTransfer": false
     }
  • Call the transferOwnership API with empty array in objects. Value of the status in this table will be inserted as a INITIATED by transfer-ownership Flink job. Sample curl

    Code Blockcurl --location --request POST '{{host}}/api/user/v1/ownership/transfer' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer {{api_key}}' \ --header 'x-authenticated-user-token: {{user_token}}' \ --data-raw '{ "request":{

  • Group - TBD

  • Send the AUDIT telemetry event after successful/failure update/deletion of the above tables.

User delete API:

Expand
titleDELETE '{{host}}/api/user/v1/delete/{{userId}}'
Code Block
curl --location --request DELETE '{{host}}/api/user/v1/delete/{{userId}}' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer {{api_key}}' \
--header 'x-authenticated-user-token: {{user_token}}'
Expand
titleResponse
Code Block
{
    "id": "api.user.delete",
    "ver": "1.0",
    "ts": "2023-08-28T13:54:45Z+05:30",
    "params": {
        "

...

resmsgid": "

...

a638c46e-63a5-47de-bf00-029cbe435e5e",
        "

...

msgid": 

...

null,
        "

...

err": 

...

null,
        "status": "successful",
        "

...

errmsg": 

...

null
    }

...

User deletion status table

This table is required for any audit purpose in future by anyone.

Code Block
CREATE TABLE sunbird.user_deletion_status(
	userId text,
	type text,	// userLookUp, userExtIdnt, keycloak, user, discussionForum, userOwnershipTransfer
	status boolean,
	createdDate timestamp,
	upatedDate timestamp,
 	PRIMARY KEY (userId, type)
 );

User delete API:

Request:

Code Block
languagejson
curl --location --request DELETE '{{host}}/api/user/v1/delete/{{userId}}' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer {{api_key}}' \
--header 'x-authenticated-user-token: {{user_token}}'

Response:

Code Block
languagejson
{
,
    "responseCode": "OK",
    "result": {
        "response": "SUCCESS",
        "userId": "{{userId}}"
    }
}

Delete User Kafka Event

Property

Description

organisationId

It helps to identify user belongs to which organisation

userId

Deleted user id

suggested_user

If user have role other than PUBLIC, than suggested user list can be send in the event for each role user is having.

Expand
titleSample event
Code Block
{
  "eid": "BE_JOB_REQUEST",
  "ets": 1619527882745,
  "mid": "LP.1619527882745.32dc378a-430f-49f6-83b5-bd73b767ad36",
  "actor": {
    "id": "
api.
delete-user
.delete
",
    "
ver
type": "
1.0
System"
,

  },
  "
ts
context":
"2023-08-28T13:54:45Z+05:30",
 {
    "
params
pdata": {

      "
resmsgid
id": "
a638c46e-63a5-47de-bf00-029cbe435e5e
org.sunbird.platform",
      
"ver": "
msgid
1.0"
:

 
null,
   }
  },
  "
err
object": 
null,
{
    "
status
id": "
successful
<deleted-userId>",
    "type": "User"
  },
  "
errmsg
edata": 
null
{
    
},
"organisationId": "<organisationId>"
    "
responseCode
userId": "
OK
<deleted-userId>",
    "
result
suggested_users": [
{
    	{
    		"
response
role": "
SUCCESS
ORG_ADMIN",
    
"userId
		"users": 
"{{userId}}"
["<orgAdminUserId>"]
    	}
}

Transfer Ownership

Ownership transfer API

...

  • This API will trigger the kafka event to transfer-ownership flink job, to precess the things asynchronously. This API will validate if “toUserId“ has all the roles of from user.

Request

Code Block
curl --location --request POST '{{host}}/api/user/v1/ownership/transfer' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer {{api_key}}' \ --header 'x-authenticated-user-token: {{user_token}}' \ --data-raw '
,
    	{
    		"role": "CONTENT_CREATOR",
    		"users": ["<contentCreatorUserId>"]
    	},
    	{
    		"
request
role":
{
 "COURSE_MENTOR",
    
		"
organisationId
users": ["
{{organisationId}}
<courseMentorUserId>"]
    	}
 
"fromUserId":
 
"{{FROM_UUID}}",
  ],
    "
context
action": "
User Deletion
delete-user",
 
//
 
"User
 
Deletion",
 "
Role Change", etc. "objects
iteration": 
[
1
  
{ "type": "Asset", // Asset, Batch, Group "toUserId": "{{TO_UUID}}", "identifiers": ["do_id1", "do_id2"] } ] } }'

Response:

Code Block
languagejson
{
    "id": "api.user.ownership.transfer",
    "ver": "1.0",
    "ts": "2023-08-28T13:54:45Z+05:30",
    "params": {
        "resmsgid": "a638c46e-63a5-47de-bf00-029cbe435e5e",
        "msgid": null,
        "err": null,
        "status": "successful",
        "errmsg": null
    },
    "responseCode": "OK",
    "result": {
        "status": "Ownership transfer event is pushed successfully!"
    }
}

Flink Job

Transfer ownership:

Sample kafka event

Code Block
{
  "eid": "BE_JOB_REQUEST",
  "ets": 1619527882745,
  "mid": "LP.1619527882745.32dc378a-430f-49f6-83b5-bd73b767ad36",
  "actor": {
    "id": "ownership-transfer",
    "type": "System"
  },
  "context": {
    "channel": "01309282781705830427",
    "pdata": {
      "id": "org.sunbird.platform",
      "ver": "1.0"
    },
    "env": "dev"
  },
  "object": {
    "id": "do_11329603741667328018",
    "type": "OwnershipTransfer"
  },
  "edata": {
    "organisationId": "{{organisationId}}"
    "fromUserId": "{{FROM_UUID}}",
    "context": "User Deletion", // "User Deletion", "Role Change", etc.
    "objects": [
          {
            "type": "Asset",   // Asset, Batch, Group
            "toUserId": "{{TO_UUID}}",
            "identifiers": ["do_id1", "do_id2"]
          }
      ]
    "action": "ownership-transfer",
    "iteration": 1
  }
}
  • Update the status to PROCESSING in sunbird.user_ownership_transfer table.

  • Asset

    • Fetch the list of created content/course/batch by fromUserId, using the composite search API.

    • Update the createdBy and creator using content system update API.

  • Batch

    • Fetch the list of open and ongoing batches created by fromUserId from sunbird_courses.course_batch table.

    • Update the user entry in sunbird_courses.course_batch table for open and ongoing batches only.

      • createdby

    • If the user is added as a course mentors to any batch, than use the search API to find out the batches and than remove the userId from mentors column in sunbird_courses.course_batch table.

  • Groups - Transfer the ownership to any other group admin if any other group admin is available else 1st assign the admin to any member and then transfer the ownership.

  • Update the status to COMPLETED.

Ownership Transfer Table

Code Block
CREATE TABLE sunbird.user_ownership_transfer_status(
	userId text,
	toUserId text,
	type text,	// Asset, Batch, Group, etc.
	identifier text,
	status text, // INITIATED, SUBMITTED, PROCESSING, COMPLETED
	createdDate text,
    createdBy text,
    updatedDate text,
    updatedBy text,
    context text, // "User Deletion", "Role Change", etc.
    organisationId text,
 	PRIMARY KEY ((userId, type), identifier)
 );

transfer-ownership ES index

  • create the new elastic search index transfer-ownership to filter the data based on different fields.

List TransferOwnership API

  • List the users to whom admin wanted to transfer ownership using user ownership transfer list API (new API). fetch the data from transfer-ownership index from ES.

Sample curl for user ownership transfer list API:

Code Block
languagejson
curl --location --request POST '{{host}}/api/user/v1/ownership/transfer/list' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer {{api_key}}' \
--header 'x-authenticated-user-token: {{user_token}}'\
--data-raw '{
    "request":{
      "organisationId": ["<organisationId>"], // Mandatory
      "status": ["INITIATED", "SUBMITED"] // Optional. If not passed in request body 
                           // then by default all the status will return.
    }
}'

Sample Response:

Code Block
languagejson
{ "id": "api.user.ownership.transfer.list", "ver": "1.0", "ts": "2023-08-28T13:54:45Z+05:30", "params": { "resmsgid": "a638c46e-63a5-47de-bf00-029cbe435e5e", "msgid": null, "err": null, "status": "successful", "errmsg": null }, "responseCode": "OK", "result": { "count": 1, "user": [ { "organisationId": <organisationId>, "userId": "<userId>", "username": "<username>", "status": 0, "roles": ["Content Creator"], "toUserId": "<transferred_userId>", "toUsername": "<toUsername>" "createdDate": "<createdDate>", "createdBy": "<created_userId>", "updatedDate": "<updatedDate>", "updatedBy": "<updatedBy_userId>" } ] } }
}
}

Deleted Users search curl

User search API can be used to get the more detail about user. e.g. name.

Expand
titleCurl

curl --location '{{host}}/api/user/v3/search' \
--header 'Authorization: {{kong_api_key}}' \
--header 'X-Authenticated-User-token: {{keycloak_access_token}}' \
--header 'Content-Type: application/json' \
--data '{
"request": {
"filters": {
"status": 2, "updatedDate": {">=": "2023-05-10 08:41:50:752+0000"}
}
}
}'

OTP Template

Expand
titleEmail Template

<html><head><meta name="viewport" content="width=device-width"><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title></title><style>@media only screen and (max-width:620px){table[class=body] h1{font-size:28px!important;margin-bottom:10px!important}table[class=body] a,table[class=body] ol,table[class=body] p,table[class=body] span,table[class=body] td,table[class=body] ul{font-size:16px!important}table[class=body] .article,table[class=body] .wrapper{padding:10px!important}table[class=body] .content{padding:0!important}table[class=body] .container{padding:0!important;width:100%!important}table[class=body] .main{border-left-width:0!important;border-radius:0!important;border-right-width:0!important}table[class=body] .btn table{width:100%!important}table[class=body] .btn a{width:100%!important}table[class=body] .img-responsive{height:auto!important;max-width:100%!important;width:auto!important}}@media all{.ExternalClass{width:100%}.ExternalClass,.ExternalClass div,.ExternalClass font,.ExternalClass p,.ExternalClass span,.ExternalClass td{line-height:100%}.apple-link a{color:inherit!important;font-family:inherit!important;font-size:inherit!important;font-weight:inherit!important;line-height:inherit!important;text-decoration:none!important}}</style></head><body class="" style="color:#000!important;background-color:#f6f6f6;font-family:sans-serif;-webkit-font-smoothing:antialiased;font-size:14px;line-height:1.4;margin:0;padding:0;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%"><table border="0" cellpadding="0" cellspacing="0" class="body" style="border-collapse:separate;mso-table-lspace:0;mso-table-rspace:0;width:100%;background-color:#f6f6f6"><tbody><tr><td style="font-family:sans-serif;font-size:14px;vertical-align:top"> </td><td class="container" style="font-family:sans-serif;font-size:14px;vertical-align:top;display:block;Margin:0 auto;max-width:580px;padding:10px;width:580px"><div class="content" style="box-sizing:border-box;display:block;Margin:0 auto;max-width:580px;padding:10px"><span class="preheader" style="color:transparent;display:none;height:0;max-height:0;max-width:0;opacity:0;overflow:hidden;mso-hide:all;visibility:hidden;width:0"></span><table class="main" style="border-collapse:separate;mso-table-lspace:0;mso-table-rspace:0;width:100%;background:#fff;border-radius:3px"><tbody><tr><td class="wrapper" style="font-family:sans-serif;font-size:14px;vertical-align:top;box-sizing:border-box;padding:20px"><table border="0" cellpadding="0" cellspacing="0" style="border-collapse:separate;mso-table-lspace:0;mso-table-rspace:0;width:100%"><tbody><tr><td style="font-family:sans-serif;font-size:14px;vertical-align:top"><table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse:separate;mso-table-lspace:0;mso-table-rspace:0;width:100%;box-sizing:border-box"><tbody><tr><td align="left" style="font-family:sans-serif;font-size:14px;vertical-align:top;font-family:sans-serif;font-size:14px;font-weight:400;margin:0;Margin-bottom:15px">Hello User,<br><p>To confirm the deletion of your $installationName account, please enter the following OTP: $otp.</p><p>This OTP is valid for the next $otpExpiryInMinutes.</p><p>If you did not request this account deletion, please ignore this message or contact our support team immediately at $supportEmail.</p><p>Thank you for using $installationName.</p>Best regards,<br>The $installationName Team</td></tr></tbody></table></td></tr></tbody></table></td></tr></tbody></table></div></td><td style="font-family:sans-serif;font-size:14px;vertical-align:top"> </td></tr></tbody></table></body></html>

Code Block
Hello User,

To confirm the deletion of your Sunbird Ed account, please enter the following OTP: [OTP Code].

This OTP is valid for the next [Time Limit, e.g., 5 minutes]. 

If you did not request this account deletion, please ignore this message or contact our support team immediately at [Support Email].

Thank you for using Sunbird Ed.

Best regards,
The Sunbird Ed Team
  • SMS

Code Block
Dear User, to confirm the deletion of your Sunbird Ed account, please enter the following OTP: [OTP Code].This OTP is valid for the next [Time Limit, e.g., 5 minutes]. If you did not request this account deletion, please ignore this message or contact our support team immediately.

Backward compatibility Approach:

As part of backward compatibility, ‘User Deletion Flink Job’ mentioned above can be deployed; ‘delete-user’ kafka topic can be created and events to the kafka topic can be inserted via scripts by passing users' information as part of the topic for completing user deletion activity.

Manage Learn -

As part of the Manage Learn use case, the user’s PII data is captured and/or used in the below-mentioned workflows -

  1. A complete snapshot of a user’s profile which includes name (first and last name) as well as masked email and phone is captured under various collections in MongoDB (observations, projects, survey, and programUsers) at the start of any transaction i.e. the moment a user starts working on a survey, or project or decides to join a program. This is done to give the Program manager the details of the user as it was when he/she started working on the resource and is not affected by his/her profile change later. This means a user’s name, location, role, and sub roles which is later used for certificate generation using Sunbird RC is the same when he/she started the resource.

  2. When the Program Manager requests reports via the Program Dashboard about the details of each and every user who has worked on a resource or has joined the program, the user’s email and phone along with the name is provided via a CSV using the Lern Data Product. These details are fetched in real-time at the moment of generating the on-demand report from the common and shared Redis and Cassandra storage. No change is required here since the expectation is user’s name, email phone will be deleted from the common storage and replaced with the “Deleted User” string, We will just need to test this once to confirm the entry from the reports is not removed but just the PII data is removed.

Info

Note - No other place in Logs, Druid, ES or Neo4j does Manage Learn workflow write to

...

  • Build a Kafka consumer in each micro-service (Survey, Projects, and ML Core) to listen to Kafka events on topic - TBD Point 4 which will do the following thing.

    • Check if any transactions are recorded for this user and if yes, remove all user name, email, and phone entries based on the userId from all collections i.e. projects, surveySubmissions, observations, observationSubmissions, and programUsers of MongoDB.

    • Update the status via API in sunbird.user_deletion_status table. Refer - TBD Point 2

  • Cron Job:

    • Run on specific intervals to do the sanity check of deletion.

  • API for other services to insert/update the data in sunbird.user_deletion_status table.

  • Build a Kafka consumer in each micro-service (Survey, Projects, and ML Core) to listen to Kafka events on topic - {{envName}}.tranfer.ownership.job.request which will do the following thing.

    • Check if any assets are owned by the deleted user and that the new owner has required platform roles (i.e. Program Manager or Program Designer), if yes update the owner/author in the collections i.e. programs, solutions of MongoDB.

    • Update the status via API in sunbird.user_ownership_transfer table. Refer - TBD Point 3

NOTE: None of the services should log the user PII data.

TBD

  • API for other services to insert/update the data .tranfer.ownership.job.request which will do the following thing.

    • Check if any assets are owned by the deleted user and that the new owner has required platform roles (i.e. Program Manager or Program Designer), if yes update the owner/author in the collections i.e. programs, solutions of MongoDB.

    • Update the status via API in sunbird.user_ownership_transfer table.

  • Kafka topic and event structure to notify BBs and services about user deletion activity.

Checklist:

  • Check with Cokreat regarding user PII information stored as part of program - No PII data is stored as part of program. User related data is stored in OpenSABER
    • Refer - TBD Point 3

NOTE: None of the services should log the user PII data.

References