Creating Mobile Apps for MIM with PowerApps and Lithnet RestAPI


I recently take a look into the Preview of Microsoft PowerApps and also Microsoft Flow. There are some really cool examples and templates for PowerApps and I also like that it is possible to create custom API connection for nearly every RestAPI.

When thinking on MIM we have that great Lithnet RestAPI from Ryan Newington and so a new project was born in my spare private time. Here are the results.

In order to create a connector in PowerApps for custom APIs we need a Swagger JSON definition file which describes the API (calls, parameters and so on). You can easily create such a definition file with the Swagger Editor in YAML. (Don’t worry, I’ve done this for you).

If you want to take a better look to the API definition file, go to http://editor.swagger.io and paste the YAML or JSON file of the Lithnet RestAPI into the editor. You will find both files at the end of the post.

These are the steps to setup the environment and create a simple PowerApp:

Install Lithnet Rest API:

First we assume you have a running MIM 2016 or FIM 2012 R2 installation. In that solution install the Lithnet FIM/MIM RestAPI according to the documentation: http://lithnetrmws.codeplex.com

Create PowerApps Connector:

Next go to http://web.powerapps.com and sign-up for an account, after that go to Manage -> Connections

01

Click on “+ New Connection” which leads you to the page with all possible default connectors. Click on “Custom”. All your custom API definitions are listed here.

02

Click on “+ New custom API” in the top right corner. You can now enter the information for the API definitions. Enter a name (ex. MIMRestAPI) upload the this JSON file, a PNG image icon and also a short description and click “Next”.

03

Since the Lithnet RestAPI by default uses Basic Authentication and this is also defined in the JSON definition file you see the selection of that auth method. Because this is the only method you cannot switch the mode. Click “Create”.

04

You now have a new connector definition for the MM RestAPI.

05

Next is to create a now connection from that connector with the authentication data so click on the “+” on the right of the connector.

06

Enter the authentication data for the RestAPI for example a MIM Portal Admin.

07

You finally have a new connection ready to use in either PowerApps or Flow. You can change the authentication data at any time on the edit mode of the connection.

 

Install PowerApp Studio and Create a Basic App:

Click on the “+ New App” button and install the PowerApps Studio to design an application of you not done this before, otherwise start the PowerApps Studio.

08

Click on “New” to create a new App.

09

Choose the “Phone layout” from the “Blank app” template.

10

You now see the App MainScreen where you can design the app, and I say design as no real development skills are necessary to build an app.

11

Let’s do a simple demo to show some data from the MIM API in the app. So click on “Connect to data” on the white page of the App and after that on “Data Sources” on the above menu. Click on “Add data source” from the sliding in action menu.

12

You now have the Lithnet data source connected to your app, “Lithnet” is the name defined in the “title” attribute of the JSON definition file.

Click on “Quick tools” on the right down corner and choose the fifth layout with the hint “Browse items, header and description”. Your app should look like the following after that:

13

Click on the middle of the app which selects the “BrowserGallery” you added by the layout. In the menu band you should now be able to modify the “items” property. Set the value of the property to:

Lithnet.GetResources({objectType:"Group"})

The app should now show some data in the browse gallery like in the following screenshot:

14

Let’s make the visual a little nicer, again click into the middle of the app so that the BrowserGallery is selected and then click “Options” button down below. In the actions pane choose “Type” and the first and “DisplayName” as the second attribute. And the app looks like following:

15

And we are done for a first demo, you can now save your app to Azure and let it run in either the Studio or the website (http://web.powerapps.com). Currently there is no PowerApp for Windows 10 Mobile (very very sad, all think are last for either Edge or Windows Phone) but you can download PowerApps also for iPhone and Android.

You can also share the app to everyone in your organization for either “use” or “use and share”, but don’t forget to also share the connector (not connection) on the custom API connector page.

 

Addition:

For getting the search box and sort function working in that demo layout set the “items” property of the BrowserGallery to the following value:

Sort(If(IsBlank(TextSearchBox1.Text), Lithnet.GetResources({objectType:"Group"}), Filter(Lithnet.GetResources({objectType:"Group"}), TextSearchBox1.Text in Text(DisplayName))), DisplayName, If(SortDescending1, SortOrder.Descending, SortOrder.Ascending))

 

Files:

Here is the JSON code:

{
    "swagger": "2.0",
    "info": {
        "version": "1.0",
        "title": "Lithnet",
        "description": "Lithnet Resource Management Web Service, Swagger Definition by Peter Stapf (MVP)",
        "contact": {
            "name": "Ryan Newington",
            "url": "http://lithnetrmws.codeplex.com"
        },
        "license": {
            "name": "Microsoft Public License (MS-PL)",
            "url": "http://lithnetrmws.codeplex.com/license"
        }
    },
    "host": "mimrestapi.yourdomain.com:8443",
    "schemes": [
        "https"
    ],
    "produces": [
        "application/json"
    ],
    "securityDefinitions": {
        "basicAuth": {
            "type": "basic",
            "description": "HTTP Basic Authentication. Works over `HTTP` and `HTTPS`"
        }
    },
    "paths": {
        "/v1/resources/": {
            "get": {
                "security": [
                    {
                        "basicAuth": []
                    }
                ],
                "tags": [
                    "resource"
                ],
                "summary": "Get resources",
                "description": "Get all resources or filter by ObjectType or XPath",
                "operationId": "GetResources",
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "parameters": [
                    {
                        "name": "objectType",
                        "in": "query",
                        "required": false,
                        "type": "string"
                    },
                    {
                        "name": "filter",
                        "in": "query",
                        "required": false,
                        "type": "string"
                    },
                    {
                        "name": "attributes",
                        "in": "query",
                        "required": false,
                        "type": "string"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The operation completed successfully",
                        "schema": {
                            "type": "array",
                            "items": {
                                "$ref": "#/definitions/resource"
                            }
                        }
                    },
                    "400": {
                        "description": "The request was malformed or incomplete"
                    },
                    "403": {
                        "description": "The user was not authorized to perform the operation"
                    },
                    "404": {
                        "description": "The specified resource could not be found"
                    },
                    "500": {
                        "description": "An internal error occured"
                    }
                }
            },
            "post": {
                "security": [
                    {
                        "basicAuth": []
                    }
                ],
                "tags": [
                    "resource"
                ],
                "summary": "Create new resource object",
                "description": "Create new resource object",
                "operationId": "CreateResource",
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "parameters": [
                    {
                        "name": "request",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/resource"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The operation completed successfully",
                        "schema": {
                            "type": "string"
                        }
                    },
                    "400": {
                        "description": "The request was malformed or incomplete"
                    },
                    "403": {
                        "description": "The user was not authorized to perform the operation"
                    },
                    "404": {
                        "description": "The specified resource could not be found"
                    },
                    "500": {
                        "description": "An internal error occured"
                    }
                }
            }
        },
        "/v1/resources/{id}/": {
            "get": {
                "security": [
                    {
                        "basicAuth": []
                    }
                ],
                "tags": [
                    "resource"
                ],
                "summary": "Get resources by objectID",
                "description": "Get resources by objectID",
                "operationId": "GetResourcesByID",
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "type": "string"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The operation completed successfully",
                        "schema": {
                            "type": "array",
                            "items": {
                                "$ref": "#/definitions/resource"
                            }
                        }
                    },
                    "400": {
                        "description": "The request was malformed or incomplete"
                    },
                    "403": {
                        "description": "The user was not authorized to perform the operation"
                    },
                    "404": {
                        "description": "The specified resource could not be found"
                    },
                    "500": {
                        "description": "An internal error occured"
                    }
                }
            },
            "put": {
                "security": [
                    {
                        "basicAuth": []
                    }
                ],
                "tags": [
                    "resource"
                ],
                "summary": "Update resource by objectID",
                "description": "Update resource by objectID",
                "operationId": "UpdateResource",
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "type": "string"
                    },
                    {
                        "name": "request",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/resource"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The operation completed successfully"
                    },
                    "400": {
                        "description": "The request was malformed or incomplete"
                    },
                    "403": {
                        "description": "The user was not authorized to perform the operation"
                    },
                    "404": {
                        "description": "The specified resource could not be found"
                    },
                    "500": {
                        "description": "An internal error occured"
                    }
                }
            },
            "delete": {
                "security": [
                    {
                        "basicAuth": []
                    }
                ],
                "tags": [
                    "resource"
                ],
                "summary": "Delete resource by objectID",
                "description": "Delete resource by objectID",
                "operationId": "DeleteResource",
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "type": "string"
                    },
                    {
                        "name": "request",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/resource"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The operation completed successfully"
                    },
                    "400": {
                        "description": "The request was malformed or incomplete"
                    },
                    "403": {
                        "description": "The user was not authorized to perform the operation"
                    },
                    "404": {
                        "description": "The specified resource could not be found"
                    },
                    "500": {
                        "description": "An internal error occured"
                    }
                }
            }
        },
        "/v1/resources/{objectType}/{anchorAttributeName}/{anchorAttributeValue}/": {
            "get": {
                "security": [
                    {
                        "basicAuth": []
                    }
                ],
                "tags": [
                    "resource"
                ],
                "summary": "Get resources by Path",
                "description": "Get resources by Path",
                "operationId": "GetResourcesByPath",
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "application/json"
                ],
                "parameters": [
                    {
                        "name": "objectType",
                        "in": "path",
                        "required": true,
                        "type": "string"
                    },
                    {
                        "name": "anchorAttributeName",
                        "in": "path",
                        "required": true,
                        "type": "string"
                    },
                    {
                        "name": "anchorAttributeValue",
                        "in": "path",
                        "required": true,
                        "type": "string"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The operation completed successfully",
                        "schema": {
                            "type": "array",
                            "items": {
                                "$ref": "#/definitions/resource"
                            }
                        }
                    },
                    "400": {
                        "description": "The request was malformed or incomplete"
                    },
                    "403": {
                        "description": "The user was not authorized to perform the operation"
                    },
                    "404": {
                        "description": "The specified resource could not be found"
                    },
                    "500": {
                        "description": "An internal error occured"
                    }
                }
            }
        }
    },
    "definitions": {
        "resource": {
            "type": "object",
            "description": "Resource object with multiple objectTypes like Person, Group, Set or other",
            "properties": {
                "AccountName": {
                    "type": "string"
                },
                "DisplayName": {
                    "type": "string"
                },
                "MailNickname": {
                    "type": "string"
                },
                "ObjectType": {
                    "type": "string"
                },
                "ObjectID": {
                    "type": "string"
                },
                "PostalCode": {
                    "type": "string"
                },
                "Address": {
                    "type": "string"
                },
                "Domain": {
                    "type": "string"
                },
                "OfficeLocation": {
                    "type": "string"
                },
                "JobTitle": {
                    "type": "string"
                },
                "EmployeeType": {
                    "type": "string"
                },
                "Department": {
                    "type": "string"
                },
                "Manager": {
                    "type": "string"
                },
                "FirstName": {
                    "type": "string"
                },
                "Assistant": {
                    "type": "string"
                },
                "City": {
                    "type": "string"
                },
                "Email": {
                    "type": "string"
                },
                "Country": {
                    "type": "string"
                },
                "LastName": {
                    "type": "string"
                },
                "OfficePhone": {
                    "type": "string"
                },
                "EmployeeID": {
                    "type": "string"
                },
                "MobilePhone": {
                    "type": "string"
                },
                "Photo": {
                    "type": "string",
                    "format": "byte"
                },
                "CreatedTime": {
                    "type": "string",
                    "format": "date-time"
                },
                "Scope": {
                    "type": "string"
                },
                "Type": {
                    "type": "string"
                },
                "Description": {
                    "type": "string"
                },
                "ExplicitMember": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                },
                "ComputedMember": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                },
                "MembershipLocked": {
                    "type": "string"
                },
                "MembershipAddWorkflow": {
                    "type": "string"
                }
            }
        }
    }
}

 

And here is the YAML code for pasting into swagger editor:

swagger: '2.0'
info:
  version: '1.0'
  title: Lithnet
  description: Lithnet Resource Management Web Service, Swagger Definition by Peter Stapf (MVP)
  contact:
    name: Ryan Newington
    url: 'http://lithnetrmws.codeplex.com'
  license:
    name: Microsoft Public License (MS-PL)
    url: 'http://lithnetrmws.codeplex.com/license'
host: 'mimrestapi.yourdomain.com:8443'
schemes:
- "https"
produces:
- "application/json"
securityDefinitions:
  basicAuth:
    type: basic
    description: HTTP Basic Authentication. Works over `HTTP` and `HTTPS`
paths:
  '/v1/resources/':
    get:
      security:
      - basicAuth: []
      tags:
        - resource
      summary: Get resources
      description: Get all resources or filter by ObjectType or XPath
      operationId: GetResources
      consumes:
        - application/json
      produces:
        - application/json
      parameters:
        - name: objectType
          in: query
          required: false
          type: string
        - name: filter
          in: query
          required: false
          type: string
        - name: attributes
          in: query
          required: false
          type: string
      responses:
        '200':
          description: The operation completed successfully
          schema:
            type: array
            items:
              $ref: '#/definitions/resource'
        '400':
          description: The request was malformed or incomplete
        '403':
          description: The user was not authorized to perform the operation
        '404':
          description: The specified resource could not be found
        '500':
          description: An internal error occured

    post:
      security:
      - basicAuth: []
      tags:
        - resource
      summary: Create new resource object
      description: Create new resource object
      operationId: CreateResource
      consumes:
        - application/json
      produces:
        - application/json
      parameters:
        - name: request
          in: body
          required: true
          schema:
            $ref: '#/definitions/resource'
      responses:
        '200':
          description: The operation completed successfully
          schema:
            type: string
        '400':
          description: The request was malformed or incomplete
        '403':
          description: The user was not authorized to perform the operation
        '404':
          description: The specified resource could not be found
        '500':
          description: An internal error occured

  '/v1/resources/{id}/':
    get:
      security:
      - basicAuth: []
      tags:
        - resource
      summary: Get resources by objectID
      description: Get resources by objectID
      operationId: GetResourcesByID
      consumes:
        - application/json
      produces:
        - application/json
      parameters:
        - name: id
          in: path
          required: true
          type: string
      responses:
        '200':
          description: The operation completed successfully
          schema:
            type: array
            items:
              $ref: '#/definitions/resource'
        '400':
          description: The request was malformed or incomplete
        '403':
          description: The user was not authorized to perform the operation
        '404':
          description: The specified resource could not be found
        '500':
          description: An internal error occured
  
    put:
      security:
      - basicAuth: []
      tags:
        - resource
      summary: Update resource by objectID
      description: Update resource by objectID
      operationId: UpdateResource
      consumes:
        - application/json
      produces:
        - application/json
      parameters:
        - name: id
          in: path
          required: true
          type: string
        - name: request
          in: body
          required: true
          schema:
            $ref: '#/definitions/resource'
      responses:
        '200':
          description: The operation completed successfully
        '400':
          description: The request was malformed or incomplete
        '403':
          description: The user was not authorized to perform the operation
        '404':
          description: The specified resource could not be found
        '500':
          description: An internal error occured

    delete:
      security:
      - basicAuth: []
      tags:
        - resource
      summary: Delete resource by objectID
      description: Delete resource by objectID
      operationId: DeleteResource
      consumes:
        - application/json
      produces:
        - application/json
      parameters:
        - name: id
          in: path
          required: true
          type: string
        - name: request
          in: body
          required: true
          schema:
            $ref: '#/definitions/resource'
      responses:
        '200':
          description: The operation completed successfully
        '400':
          description: The request was malformed or incomplete
        '403':
          description: The user was not authorized to perform the operation
        '404':
          description: The specified resource could not be found
        '500':
          description: An internal error occured

  '/v1/resources/{objectType}/{anchorAttributeName}/{anchorAttributeValue}/':
    get:
      security:
      - basicAuth: []
      tags:
        - resource
      summary: Get resources by Path
      description: Get resources by Path
      operationId: GetResourcesByPath
      consumes:
        - application/json
      produces:
        - application/json
      parameters:
        - name: objectType
          in: path
          required: true
          type: string
        - name: anchorAttributeName
          in: path
          required: true
          type: string
        - name: anchorAttributeValue
          in: path
          required: true
          type: string
      responses:
        '200':
          description: The operation completed successfully
          schema:
            type: array
            items:
              $ref: '#/definitions/resource'
        '400':
          description: The request was malformed or incomplete
        '403':
          description: The user was not authorized to perform the operation
        '404':
          description: The specified resource could not be found
        '500':
          description: An internal error occured

definitions:
  resource:
    type: object
    description: Resource object with multiple objectTypes like Person, Group, Set or other
    properties:
      AccountName:
        type: string
      DisplayName:
        type: string
      MailNickname:
        type: string
      ObjectType:
        type: string
      ObjectID:
        type: string
      PostalCode:
        type: string
      Address:
        type: string
      Domain:
        type: string
      OfficeLocation:
        type: string
      JobTitle:
        type: string
      EmployeeType:
        type: string
      Department:
        type: string
      Manager:
        type: string
      FirstName:
        type: string
      Assistant:
        type: string
      City:
        type: string
      Email:
        type: string
      Country:
        type: string
      LastName:
        type: string
      OfficePhone:
        type: string
      EmployeeID:
        type: string
      MobilePhone:
        type: string
      Photo:
        type: string
        format: byte
      CreatedTime:
        type: string
        format: date-time
      Scope:
        type: string
      Type:
        type: string
      Description:
        type: string
      ExplicitMember:
        type: array
        items:
          type: string
      ComputedMember:
        type: array
        items:
          type: string
      MembershipLocked:
        type: string
      MembershipAddWorkflow:
        type: string

 

Advertisements

About Peter Stapf
Senior Consultant Identity and Access MVP (Enterprise Mobility)

7 Responses to Creating Mobile Apps for MIM with PowerApps and Lithnet RestAPI

  1. tlkidentity says:

    A+ Peter. I got to re-blog this

  2. Peter Stapf says:

    thanxs Ike, feel free to do so.
    BTW I got also a speech input for working with Cortana and Bing Speech (Cognitive Services). Its just a small PoC but I will do a post next week I think.

  3. tlkidentity says:

    Reblogged this on tlktechidentitythoughts and commented:
    This is an brilliant post from Peter on how to create apps and integrate with MIM. Using RESTAPI and Json. There are so many ships that one can build on this foundational post from Peter.

  4. Pingback: Create PAM (Privileged Access Management) Mobile Apps with PowerApps | JustIDM

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: