Skip to Content
Technical Articles
Author's profile photo Christian Sørensen

SAC: User management with the REST API (Python)

Introduction

In this blog post, you will learn the ins and outs of the user management components of the SAC API. You will learn how to create, maintain and delete users and teams, how to receive user and team information, including several “hacks” to get even more out of your SAC tenant.

Furthermore, you’ll find a bibliography of useful resources, API documentation and a fluly fletched Python API, with all the code free to use.

In short, this should be your one-stop-shop for all things SAC API.

This blog post is structured as follows:

  1. Configurations:
    1. Configuring your SAC tenant
    2. Acquiring Python and the required modules.
  2. Exploring the SAC API
  3. (Optional) Testing the SAC API with postman
  4. Introduction to the GIT repository
  5. Working code examples and a walkthrough.
  6. Getting all roles: A workaround

1. Configurations

In this section, we’ll explore the required configurations to your SAC tenant, before you can use the SAC API.

1.1 SAC Configuration

Your SAC tenant contains several pieces of information you’ll need to get the API interaction to work.

The base url:

The first piece is the base URL of your sac tenant.

Go to your SAC landingpage and copy the URL. You’ll need the bit below.

https://<THIS BIT>/sap/fpa/ui/app.html#/home. 

Note that information down.

Configuring your SAC client

To interact with the SAC API, you first need to open a channel. To do this, you will need access to a SAC tenant and rights to view and change System > Administration > App Integration

How to:

  • Log into your SAC tenant.

  • From there, choose “System information” -> “Administration”

  • From there, choose “App integration” and choose “Add a new OAuth Client”

  • Give your new client a descriptive “Name”, configure the “Purpose” to “… API Access” and choose “User provisioning” from the “Access” menu. Then press “Add”.

  • Your SAC tenant will now add the client. This will take several minutes.

Once your client is loaded and created, you’ll need to note down several pieces of information:

  • Your SAC tenant’s oAuth2SAML Token URL:

Under “App Integration”, you’ll see several sections, the top one being “OAuth Clients”.

Locate the oAuth2SAML Token URL and copy it into a notepad or similar.

Scroll down a bit to the “Configured Clients” section, and choose “Edit” on your newly created OAuth Client.

You’ll see a OAuth Client ID and a Client Secret. Copy both down.

Conclusion:

You should now be in possession of four pieces of information:

  • Base SAC url
  • oAuth2SAML Token URL
  • Client ID
  • Client Secret

You’ll need these in plain text.

1.2 Configuring an IDE

An IDE (Integrated Development Environment) is a catch-all term for a tool for writing and executing code. Personally, I prefer VS Code and have used it for developing the code below, but you can choose any IDE you want.

You can download VS Code for your preferred platform, here.

If you want to follow along, or make alterations to the code below, you’ll need to install Python and download an assortment of modules.

Again, I personally prefer the Anaconda distribution, which you can download here, but the in-built pip distribution should work just fine.

Once you have Python installed, using either pip or Anaconda, install the following Python modules:

# With pip:
pip install json
pip install requests

# With Anaconda:
conda install json
conda install requests

2. Exploring the SAC API

Now that your basic configurations is in order, it’s time to explore the SAC API.

The SAC API has six endpoints, with different purposes. In this guide, we’ll be exploring two of those endpoints:

  • Users
  • Groups

You can find all the endpoints here, if you are interested or want to explore the possibilities.

Both endpoints allow all the familiar CRUD methods:

  • GET
  • POST
  • PUT
  • DELETE

And we’ll explore what they can do in turn.

GET: Receiving information

The GET method can do two things. It can return a full list of users in your SAC tenant, or the information regarding a single user.

If you want to test this endpoint, you can hit it from your browser, if you’re already logged into you SAC tenant.

Getting users:

Append “/api/v1/scim/Users” to your base url and press enter.

This should give you something along these lines, with all your users:

Getting a single user:

You can, if you want, add “/USER_ID” and get only a single user back.

This can be very helpful if you want to see how the different values in your user is represented.

Getting teams:

Similar to users, you can fetch a full list of teams from the endpoint “api/v1/scim/Groups”.

Getting a single team:

You would think now, that adding “/TEAM_ID” would then return a single team.
However, that is an error in SAC, which will impact how you can search single teams.

In short, teams created from the SAC front-end cannot be found via the API. For the API to be able to return a single team, rather than the full list, you’ll need to create the team by the API, or implement a fix on your cloud tenant. you can find the full story in SAP note 2859395.

This will be somewhat important later, where we’ll use a little trick to get a list of roles.

POST: Creating an entity

The POST method is used to create an entity; user or team.

To create a user, you’ll have to provide a “request body” to the API, containing the needed information. You can the required body at the API documentation, under “POST”.

As for now, I’ve included the request body in the Pyhton API, so you shouldn’t have to worry too much about it 🙂

PUT: Updating an entity

The PUT request is made towards a single entity, and is used to update a single user or team.
This can be used to update an email, assign roles or assign users to a team.

Similar to the POST method, PUT requires a list of information in a certain format (schema), and will use that information to update a user or team.

An important note to make with PUT requests, is that they are considered “full” loads.
In practice, this means that updating a user or team, will usually to a several-step process: (1) Getting the existing from the user (2) updating the values you want and (3) PUTting it back to SAC.

DELETE: Removing an entity

As the name suggests, this method is used to delete a team of a user.
Making a DELETE call to “api/v1/scim/Users/TEST_USER”, will delete the user.

Conclusion:

The SAC API allows you to create, update, get and delete both users and teams.
All these operations require a proper URL, and both POST and PUT both require a request body.

 

3. Testing the API with Postman

Before we get into the code, you can run some preliminary tests of your API with any HTTP caller.
Personally i prefer Postman, which is a perfect lightweight tool for these kinds of situation.

Doing so, will help you understand the flow of the Python program. If you are well versed in HTTP calls, you can comfortably skip this chapter.

You can access Postman here.

Step 1: Receiving the ‘Bearer Token’.

A bearer token is a OAuth 2.0 artifact, used for identifying a sender as an authorized entity.
The token itself is a string of utter nonsense, and is in itself not very interesting. It is, however, required for calls.

Create a “POST” request.

Add the following information:

  • In the “URL” insert your oAuth2SAML Token URL.
  • Under the “Authorization” tab, choose “Basic Auth.”
  • In username and password, insert your client ID and client password, respectively.

Click “SEND”.

In the “body” section of the response, you’ll find the “Bearer” token:

Step 2: Getting a list of users

Now that you have the bearer token, you can make the GET requests, described above.

Create a new GET request. In the “Header” section, add the following three pieces of information:

KEY VALUE
Authorization Bearer <YOUR BEARER TOKEN>
x-sap-sac-custom-auth true
x-csrf-token fetch

(Note: The last piece will make sense in a moment)

In the URL, insert your base SAC URL, appended by the endpoint you want to explore. In this example, i’ve used the “Users” endpoint.

Click “Send”, and you should get a list of your users back:

Step 3: Making POST and PUT requests

Creating entities in a database is a somewhat different from getting information out, and as such, it requires an extra bit of security: An CSRF token.

This token can be obtained as extra information on other calls – such as the one we just made.
The line in the “Header” section: “csrf-token : fetch” means, that your SAC tenant will return a unique token in it’s header section:

Note this token down.

Be advised: As soon as your end this browser session, you’ll need to fetch a new token.

Now that you have your csrf token, you are now allowed to create and edit entities on your SAC system.

Create a new request and mark it as “POST”.

Add the following information in the header section:

KEY VALUE
x-sap-sac-custom-auth true
x-csrf-token <YOUR CSRF TOKEN>
Content-Type application/json
Authorization Bearer <YOUR BEARER TOKEN>

And in the “body” section paste the following piece of JSON, and replace the required sections:

{
        "schemas": ["ietf:params:scim:schemas:core:2.0:User", "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"],
        "userName": "<ID>",
        "name":
        {
            "givenName": "<FIRST NAME>",
            "familyName": "<LAST NAME>"
        },
        "displayName": "<DISPLAY NAME>",
        "emails": [
            {
                "value": "<WORK EMAIL>",
                "type": "work",
                "primary": "true"
            }
        ],
        "roles": [],
        "groups": [],
        "urn:scim:schemas:extension:enterprise:1.0": {
            "manager": {
                "managerId": ""
            }
        }
    }

When you click send, you should, if you have done everything correctly, get a 201 response, and your user should have been created. This will return a “201”

If you want, you can do the exact same thing with the /Groups endpoint to create a team 🙂

Conclusion:

You’ve now seen how to interact with the API, and seen the basic interactions between the API and HTTP-requests. You’re now ready to see the Python code 😎

4. The SAC API Python code

So, what do we have so far?
We have the information needed to get access to the API, and we have a basic understanding on what the API allows us to do.

In this section, I’ll run through the code created to utilize the different aspects of the API.
If you wanted to create a similar for your 3rd party app, you can use this as inspiration.

I’ve created a Python package that will allow you to do the following:

  1. Get a list of all users
  2. Getting information on one user
  3. Getting all teams
  4. Creating a team
  5. Creating a user
  6. Assign roles & teams to a user

How you choose to use the different function, is all up to you.

The code:

All code can be found, cloned or downloaded from the following github repo.

Known issues and TODOs:

  1. As of now, the code requires the information you noted down earlier to be stored in plain text.
    This is not ideal, and is a severe limit on the usability of the module for non-python users.
    Not that is is hard, or difficult, but it may be off-putting to some.
  2. As of now, no functions exists to delete users and/or teams in the Python package. This is on the todo-list.

The flow of the program:

As you saw in section 3, the flow of the Python program is as follows:

  1. Based on the requirement, construct a URL
  2. Fetch the bearer token
  3. Construct the basic header information. If you’re making a POST or PUT request, the csrf token will also be generated.
  4. Make the request
  5. Return the response.

The classes:

The module includes five classes:

  • UrlConstructor: Class for constructing a URL for requests

This class has one attribute, sacBaseUrl, and a single method. You will need to open the file and add your baseUrl to to the file.

The method, is a helper function to generate the URL.

if, for example, you were to call fetchUrl(“users”), you would get your baseUrl back, appended by ‘/api/v1/scim/Users’.

class UrlConstructor:

    # Add the basic url for your SAC tenant:
    # You should be able to find the base url from you SAC landing page:
    # https://<THIS PART>/sap/fpa/ui/app.html#/home
    sacBaseUrl = ""

    def __init__(self):
        print("URL constructor instansiated")

    def __str__(self):
        print("Helper class: Fetching URLs and tokens")

    # Function for contructing request URLs.
    # Returns the base url,
    def fetchUrl(endpoint: Literal["bearer", "csrf", "users", "groups", "user", "group"], entity=""):
        '''Returns a url & endpoint. Note: User & Group requires the optional "entity" format'''
        if UrlConstructor.sacBaseUrl == "":
            raise ValueError("Base URL not defined")

        if endpoint == 'bearer':
            # The bearer URL is retrived from SAC -> Settings -> App. Integration -> Client.
            return str(HeaderConstructor.oAuth2SAMLTokenUrl + 'grant_type=client_credentials')
        elif endpoint == 'csrf':
            return str(UrlConstructor.sacBaseUrl + '/api/v1/scim/Users')
        elif endpoint == 'users':
            return str(UrlConstructor.sacBaseUrl + '/api/v1/scim/Users')
        elif endpoint == 'groups':
            return str(UrlConstructor.sacBaseUrl + '/api/v1/scim/Groups')
        elif endpoint == 'user':
            return str(UrlConstructor.sacBaseUrl + '/api/v1/scim/Users' + '/' + entity)
        elif endpoint == 'group':
            return str(UrlConstructor.sacBaseUrl + '/api/v1/scim/Groups' + '/' + entity)
        else:
            pass
  • HeaderConstroctor:  Class for constructing the http headers required.

The class is not really intended for direct use, but is a helper class for the other classes.

Like the URL class, you will need add a few points of information yourself.

class HeaderConstructor:

    # The bearer URL is retrived from SAC -> Settings -> App. Integration ->OAuth Clients.
    # Note: Some guides you find online might use the 'Token URL'. This procuded errors when i tried.
    oAuth2SAMLTokenUrl = ""

    # Both Client ID and secret are retrived from SAC -> Settings -> App. Integration -> Client.
    clientId = ""  # <-- <INSERT CLIENT ID>
    clientSecret = ""  # <-- <ENTER CLIENT SECRET>

    def __init__(self):
        print("Header constructor instansiated")

    def __str__(self):
        print("Helper class: Constructing header information")

    # Function for fetching the bearer token.
    # Client ID and secret are generated by your SAC tenant.
    # Follow the first step ("Configuration SAP Analytics Cloud") in this guide:
    # http://www.sapspot.com/how-to-use-rest-api-in-sap-analytics-cloud-to-update-user-profile-in-embedded-scenarios/,
    # to set up the provisioning agent.
    def getToken():
        '''Function for fetching the bearer token'''
        url = UrlConstructor.fetchUrl('bearer')

        # Thanks to Stefan Himmelhuber: Blog post:
        # https://blogs.sap.com/2020/02/06/sac-export-user-list-by-rest-api/
        # for a clever encoding method :)

        Auth = '{0}:{1}'.format(
            HeaderConstructor.clientId, HeaderConstructor.clientSecret)

        headers = {
            'Authorization': 'Basic {0}'.format(
                base64.b64encode(Auth.encode('utf-8')).decode('utf-8'))}

        params = {'grant_type': 'client_credentials'}

        tokenRequest = requests.post(url, headers=headers, params=params)

        # HTTP ERROR Handling.
        if not str(tokenRequest.status_code) == '200':
            ErrorHandling.httpRequestError(
                tokenRequest.status_code, "getToken", url, tokenRequest.json())

        returnToken = tokenRequest.json()['access_token']

        return returnToken
    # Function for fetching the CSRF-token,

    def getCsrfToken():
        '''Returns the csrf token, for PUT and POST requests'''
        # Getting the CSRF token.
        url = UrlConstructor.fetchUrl('csrf')
        headers = HeaderConstructor.getHeaders()

        csrfRequest = requests.get(url, headers=headers)

        if not str(csrfRequest.status_code) == '200':
            ErrorHandling.httpRequestError(
                csrfRequest.status_code, "getToken", url, csrfRequest.json())

        return_payload = {}
        return_payload['Token'] = csrfRequest.headers['x-csrf-token']
        return_payload['Session'] = csrfRequest.headers['set-cookie']
        return return_payload

    def getHeaders(requestType="GET"):
        '''Returns the custom header, needed for the different requests. 
        POST & PUT requests also generates the required the csrf-token'''

        #Ini. dict
        headers = {}

        # Required constants for the SAC API.
        headers['x-sap-sac-custom-auth'] = "true"
        # Custom section:
        headers['Authorization'] = 'Bearer ' + HeaderConstructor.getToken()

        # Additions for the different types of calls.
        # POST and PUT requires extra info; whereas GET only requires the Bearer.
        if requestType == "POST" or requestType == "PUT":
            csrfInfo = {}
            csrfInfo = HeaderConstructor.getCsrfToken()
            headers['Cookie'] = csrfInfo['Session']
            headers['x-csrf-token'] = csrfInfo['Token']
            headers['Content-Type'] = 'application/json'
        else:
            # Optional. dosen't hurt. Used in the GET call to fetch the token.
            headers['x-csrf-token'] = 'fetch'

        return headers
  • BodyConstructor: Class for getting the request body for the requests.
  • UserManagement: Class for all CRUD operations against the SAC API.
class UserManagement:

    def __init__(self) -> None:
        return "User management constructor instansiated"

    def __str__(self) -> str:
        return "Helper class: Manageing users and teams"

    def getGroups(returnType: Literal["json", "custom"], selectEntities=[], teams=[]):
        ''' Returns all groups, now called Teams, from SAC, eigther as raw json or select entities.
              Valid entities: "id", "displayName", "meta", "members", "roles"
              Specify a team ID in teams, for query a subset of teams. 
              '''

        urlList = []
        groupList = []

        # If list "Teams" was provided.
        if teams:
            for team in teams:
                url = UrlConstructor.fetchUrl('group', entity=team)
                urlList.append(url)
        else:
            url = UrlConstructor.fetchUrl('groups')
            urlList.append(url)

            # Get the request headers.
        headers = HeaderConstructor.getHeaders()

        if returnType == "custom":
            entityList = []
            MatchList = ["id", "displayname", "meta", "members", "roles"]
                # Collect the wanted entities and weed out the bad ones
            for entity in selectEntities:
                # Validate the entity is in the match list, otherwise pass.
                if not str(entity).lower() in MatchList:
                        continue  # <-- Discard
                entityList.append(entity)

            # Loop through the list of URLs.
            # For each url,
        for url in urlList:
            groupRequest = requests.get(url, headers=headers)

            if not str(groupRequest.status_code) == '200':
                ErrorHandling.httpRequestError(
                groupRequest.status_code, "getToken", url, groupRequest.json())

            if returnType == "json":
                groupList.append(groupRequest.json())

            if returnType == "custom":
                customPayload = {}
                groupRequestJson = groupRequest.json()

                # Collect the desired data into groupList
                for entity in entityList:
                    customPayload[entity] = groupRequestJson.get(entity)

                groupList.append(customPayload)

        return groupList

    def getUsers(returnType: Literal["json", "custom"], selectEntities=[], users=[]):
        ''' Returns all users from SAC, eigther as raw json or select entities.
            "userName", "id", "preferredLanguage", "meta", "name",  "members", "roles",
                "displayName", "active", "emails", "photos", "roles", "groups"
            '''

        urlList = []
        userList = []

        if users:
            for user in users:
                url = UrlConstructor.fetchUrl('user', entity=user)
                urlList.append(url)
        else:
            url = UrlConstructor.fetchUrl('users')
            urlList.append(url)

        if returnType == "custom":
            customPayload = {}
            entityList = []
            # Valid entities in the SAC user schema.
            MatchList = [
                "userName", "id", "preferredLanguage",
                "meta", "name",  "members", "roles",
                "displayName", "active", "emails",
                "photos", "roles", "groups"]

        # Collect the wanted entities and weed out the bad ones
        for entity in selectEntities:
            # Validate the entity is in the match list, otherwise pass.
            if not str(entity).lower() in MatchList:
                continue  # <-- Discards the entity.

            entityList.append(entity)

        headers = HeaderConstructor.getHeaders()

        for url in urlList:
            userRequest = requests.get(url, headers=headers).json()

        if returnType == "json":
            return userRequest.json()

        if returnType == "custom":
            # Collect the desired data into groupList
            for user in userRequest["Resources"]:
                for entity in entityList:
                    customPayload[entity] = user.get(entity)

                userList.append(customPayload)

        return userList

    def assignPrivileges(userId, roles=[], teams=[]):

        if not roles and not teams:
            raise ValueError("No roles or teams provided")

        url = UrlConstructor.fetchUrl('user', userId)
        headers = HeaderConstructor.getHeaders("PUT")

        # Construct body:

        # Fetch the body of requests, in order to make changes.
        userBodyRequest = requests.get(url, headers=headers)
        if not str(userBodyRequest.status_code) == '200':
            ErrorHandling.httpRequestError(
                userBodyRequest.status_code, "getToken", url, userBodyRequest.json())

        userBody = userBodyRequest.json()
        # Manipulate request body.
        userBody["roles"] = roles
        userBody["groups"] = teams

        putAssign = requests.put(url, headers=headers,
                                 data=json.dumps(userBody))
        if not str(putAssign.status_code) == '200':
            ErrorHandling.httpRequestError(
                putAssign.status_code, "getToken", url, putAssign.json())

        return putAssign.json()

    def createUser(userName, familyName, emails,
                   firstName="", roles=[], teams=[], managerId=""):

        # NOTE: Users, not a single user.
        url = UrlConstructor.fetchUrl('users')
        headers = HeaderConstructor.getHeaders("POST")

        userBody = BodyConstructor.getRequestBody('create user')

        # Format Teams
        teamsBody = []

        # Format teams into correct SCHEMA.
        for team in teams:
            templateBody = BodyConstructor.getRequestBody('add team')
            templateBody["value"] = str(team).upper()
            templateBody["$ref"] = "/api/v1/scim/Groups/" + str(team).upper()

            teamsBody.append(templateBody)

        # Non required Body elements that can be manipulated:
        displayName = firstName + ' ' + familyName  # <-- Display name*

        # Assigning custom values to the
        userBody["userName"] = userName
        userBody["name"]["firstName"] = firstName
        userBody["name"]["familyName"] = familyName
        userBody["displayName"] = displayName
        userBody["emails"][0]["value"] = emails
        userBody["urn:scim:schemas:extension:enterprise:1.0"]["manager"]["managerId"] = managerId
        userBody["roles"] = roles
        userBody["groups"] = teamsBody

        postCreate = requests.post(
            url, headers=headers, data=json.dumps(userBody))

        return postCreate.json()

    def createTeam(teamId, teamTxt, members=[], roles=[]):

        url = UrlConstructor.fetchUrl('groups')
        headers = HeaderConstructor.getHeaders('POST')

        teamBody = BodyConstructor.getRequestBody('create team')

        memberBody = []
        # Format members into correct SCHEMA.
        for user in members:
            templateBody = BodyConstructor.getRequestBody('add user')
            templateBody["value"] = str(user).upper()
            templateBody["$ref"] = "/api/v1/scim/Users/" + str(user).upper()

            memberBody.append(templateBody)

        teamBody["id"] = teamId
        teamBody["displayName"] = teamTxt
        teamBody["members"] = members
        teamBody["roles"] = roles

        postCreate = requests.post(
            url, headers=headers, data=json.dumps(teamBody))

        return postCreate.json()

    def updateUser(userName, familyName, emails,
                   firstName="", roles=[], teams=[], managerId=""):

        url = UrlConstructor.fetchUrl('user', entity=userName)
        headers = HeaderConstructor.getHeaders("PUT")
        userBody = BodyConstructor.getRequestBody('create user')

        # Format Teams
        teamsBody = []

        # Format teams into correct SCHEMA.
        for team in teams:
            templateBody = BodyConstructor.getRequestBody('add team')
            templateBody["value"] = str(team).upper()
            templateBody["$ref"] = "/api/v1/scim/Groups/" + str(team).upper()

            teamsBody.append(templateBody)

        # Non required Body elements that can be manipulated:
        displayName = firstName + ' ' + familyName  # <-- Display name*

        # Assigning custom values to the
        userBody["userName"] = userName
        userBody["name"]["firstName"] = firstName
        userBody["name"]["familyName"] = familyName
        userBody["name"]["displayName"] = displayName
        userBody["emails"]["value"] = emails
        userBody["urn:scim:schemas:extension:enterprise:1.0"]["manager"]["managerId"] = managerId
        userBody["roles"] = roles
        userBody["Teams"] = teamsBody

        postCreate = requests.put(
            url, headers=headers, data=json.dumps(userBody))

        return postCreate.json()

    def updateTeam(teamId, teamTxt, members=[], roles=[]):

        url = UrlConstructor.fetchUrl('group', entity=teamId)
        headers = HeaderConstructor.getHeaders('PUT')

        teamBody = BodyConstructor.getRequestBody('create team')

        memberBody = []
        # Format members into correct SCHEMA.
        for user in members:
            templateBody = BodyConstructor.getRequestBody('add user')
            templateBody["value"] = str(user).upper()
            templateBody["$ref"] = "/api/v1/scim/Users/" + str(user).upper()

            memberBody.append(templateBody)

        teamBody["teamId"] = teamId
        teamBody["dislpayName"] = teamTxt
        teamBody["members"] = members
        teamBody["roles"] = roles

        postCreate = requests.put(
            url, headers=headers, data=json.dumps(teamBody))

        return postCreate.json()
  • ErrorHandling: A small message class, for improved error messages.

6.  Using the code:

So, now that you have seen configured your SAC tenant, understand the scope of the API and have seen the code, you are now ready to try it out.

1. Download the files

First, go to the github repo, and download the files:

2. Extract the files unto your desktop

Once the extraction is complete, fetch the path:

3. Open the folder in VS Code:

go to VS Code, and open the folder from your path:

and open the folder. You should see something like this:

4. Insert your information into SAC.py

Open the SAC.py file and add the required information to the UrlConstructor and HeaderConstructor Class

UrlConstructor:

HeaderConstructor:

Save the SAC.py file.

5. Build your program

In the same folder; add a new file. You can name it whatever you want, but i’ll go with myApp.py.

Inside your new Python file, you can now:

import SAC

(Again, assuming you are in the correct folder structure).

Now that you have imported the module, you can begin to play around with the functions.

Getting the groups:

In the example below, we will get two results:

  1. The first line will just return a json dumb of all the teams in your SAC tenant.
    This can be very useful if you are building an app and just need the raw data.
  2. The second line will return a similar list of teams, but only the team description and the users.
#Returns all teams, as json. 
print("Raw json:")
teams = SAC.UserManagement.getGroups('json')
print(teams)

#Returns all teams, but only the team text and the assigned users
print("Custom list:")
teamsTwoColumn = SAC.UserManagement.getGroups('custom',selectEntities=['displayName','members'])
print(teamsTwoColumn)

Output:

Creating a user and assigning privileges:
#Create a user, with statis values. 
userRequest = SAC.UserManagement.createUser("<ID>", "Sorensen", "<EMAIL>","Chris") 
#Assign roles to the user:
privRequest = SAC.UserManagement.assignPrivileges("<ID>", roles=["PROFILE:sap.epm:Viewer", "PROFILE:t.S:cso_test"])

Result in SAC:

Getting the roles – a workaround

One last point here is that the API does, at the time of writing (Oct. 2021), not contain any endpoint for the roles defined in your SAC tenant.
Personally, I suspect the schema needed for the content of the role contains so many references to the other objects, that it will take considerable effort on the part of SAP.

But! There is a way around it 🙂

If you’ve explored the Groups endpoint, you’ll see that a each team also contains a list of roles in that team. No metadata, such as a description or content, but a list of roles nonetheless.

So, the workaround is as such:

  1. Use the API to create a team, that will act as a role-folder
SAC.UserManagement.createTeam(teamId="ROLES_FOLDER",teamTxt="Team for containing all roles",roles=[])

This will create a team, and make it searchable by the API:

From your SAC landindpage, go to “Security” -> “Roles” and assign each role to the team.

Now, running the following code will return a list of all your roles:

allRoles = SAC.UserManagement.getGroups('custom', selectEntities= ["roles"], teams= ["ROLES_FOLDER"])
print(allRoles)

output:

[{'roles': ['PROFILE:sap.epm:Application_Creator', 'PROFILE:sap.epm:BI_Admin', 'PROFILE:sap.epm:BI_Content_Creator']}]

 

Final conclusion

Welcome to the end!

Hopefully, you’ve been through everything with success, and have a good sense of what the SAC API can do, in terms of user management.

You should have learned:

  • Understanding the possibilities and capabilities of the SAC API
  • Understanding the various components, such as tokens, bodies ect., and how they interact.
  • See an example of how the the different components can be strung together in code, to create a robust integration between your SAC tenant and any other platform you may have.

Have a good one, and happy coding! 🤟😁

 

Assigned tags

      2 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Denys van Kempen
      Denys van Kempen

      Nice article, Christian. Thanks

      We just posted a number of tutorial videos on the topic, using Postman as client.

      Leveraging the API calls in a Python app is an good example of implementation.

      Author's profile photo Christian Sørensen
      Christian Sørensen
      Blog Post Author

      Hey Denys, Thanks! 🙂

      Really like your videos and I agree Postman is a fantastic tool for exploring webservices like these.