Using Python Dash to build custom solutions requiring a UI

Problem

Building quick custom solutions requiring a UI

Solution

Dash is a powerful Python framework for building analytical web applications. It provides a simple and intuitive way to create interactive dashboards and data visualization interfaces using Python. Dash combines the flexibility of Python with the power of modern web technologies like React and Flask. See: Plotly Dash App Examples

In the context of Identity Security Cloud solutions, Dash can be an excellent choice for several reasons:

  1. Rapid Application Development: Dash allows developers to quickly build and deploy web applications without the need for extensive web development knowledge. Its declarative approach to building user interfaces makes it easy to create interactive dashboards and data-driven applications.

  2. Integration with APIs: Dash integrates seamlessly with various APIs, including the Identity Security Cloud API. With Dash, you can easily make HTTP requests to the API, retrieve data, and display it in a user-friendly manner. This enables you to build custom dashboards and interfaces that interact with the Identity Security Cloud platform.

  3. Data Visualization: Dash provides a wide range of built-in components for data visualization, such as charts, graphs, and tables. These components allow you to present identity-related data in a visually appealing and interactive way, making it easier for users to understand and explore the information.

  4. Customization and Extensibility: Dash offers extensive customization options, allowing you to tailor the application’s appearance and behavior to match your specific requirements. You can leverage CSS and custom components to create a consistent and branded user experience. Additionally, Dash’s component-based architecture enables you to extend and reuse components across different parts of your application.

  5. Python Ecosystem: Dash is built on top of Python, which has a rich ecosystem of libraries and tools for data analysis, machine learning, and security. By using Dash, you can leverage the power of Python and its vast collection of libraries to enhance your Identity Security Cloud solutions with advanced analytics, anomaly detection, and other data-driven capabilities.

Overall, Dash provides a powerful and flexible framework for building custom web applications and dashboards that integrate with the Identity Security Cloud API. Its ease of use, data visualization capabilities, and extensibility make it a compelling choice for developing identity security solutions that require interactive user interfaces and data-driven insights.

Code Explanation:

This code is an example of building a search on identities based on the displayName attribute. This document is assuming basic knowledge with Python scripts / applications.

  1. import dash: This line imports the Dash library, which is the main library for building the web application.

  2. import dash_core_components as dcc: This line imports the Dash Core Components, which are UI components provided by Dash for building interactive web applications.

  3. import dash_html_components as html: This line imports the Dash HTML Components, which are used to create HTML elements in the application layout.

  4. from dash.dependencies import Input, Output, State: This line imports the necessary dependencies from Dash for defining callbacks and managing application state.

  5. import requests: This line imports the requests library, which is used to make HTTP requests to the Identity Security Cloud API.

  6. import json: This line imports the json library, which is used to parse and stringify JSON data.

  7. import dash_bootstrap_components as dbc: This line imports the Dash Bootstrap Components library, which provides pre-built UI components styled with Bootstrap.

  8. app = dash.Dash(__name__, suppress_callback_exceptions=True, external_stylesheets=[dbc.themes.BOOTSTRAP]): This line creates a Dash application instance with the specified options, including suppressing callback exceptions and using Bootstrap stylesheets.

  9. app.layout = html.Div([...]): This line defines the layout of the application using Dash HTML Components. It includes input fields for tenant name, client ID, client secret, identity search, and a submit button.

  10. @app.callback(...): This is a Dash callback decorator that defines a callback function to be executed when certain inputs change or buttons are clicked.

  11. def search_identity(n_clicks, identity_search, tenant, client_id, client_secret):: This is the callback function that is executed when the submit button is clicked or the identity search input changes. It takes the necessary inputs as arguments.

  12. The function first checks if the necessary inputs are provided and if the OAuth token is already retrieved. If not, it retrieves the OAuth token using the provided client credentials.

  13. If an identity search query is provided, the function makes an API call to the Identity Security Cloud search endpoint to search for identities matching the query.

  14. The API response is then parsed and the relevant identity information is extracted and formatted into Dash HTML Components.

  15. The formatted identity information is returned as the output of the callback function and displayed in the application layout.

  16. The code also includes error handling to catch and display any errors that occur during the API requests.

  17. Finally, the application is run using app.run_server(debug=True)

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import requests
import json
import dash_bootstrap_components as dbc

app = dash.Dash(__name__, suppress_callback_exceptions=True, external_stylesheets=[dbc.themes.BOOTSTRAP])

app.layout = html.Div([
    html.H1('Identity Search'),
    html.Div([
        html.Label('Tenant Name', style={'margin-right': '10px'}),
        dcc.Input(
            id='tenant-input',
            type='text',
            placeholder='Enter your tenant name'
        )
    ], style={'display': 'flex', 'align-items': 'center', 'margin-bottom': '10px'}),
    html.Div([
        html.Label('Client ID', style={'margin-right': '10px'}),
        dcc.Input(
            id='client-id-input',
            type='text',
            placeholder='Enter your client ID'
        )
    ], style={'display': 'flex', 'align-items': 'center', 'margin-bottom': '10px'}),
    html.Div([
        html.Label('Client Secret', style={'margin-right': '10px'}),
        dcc.Input(
            id='client-secret-input',
            type='password',
            placeholder='Enter your client secret'
        )
    ], style={'display': 'flex', 'align-items': 'center', 'margin-bottom': '20px'}),
    html.Button('Submit', id='submit-button', n_clicks=0),
    html.Div([
        html.Label('Identity Search', style={'margin-right': '10px'}),
        dcc.Input(
            id='identity-search',
            type='text',
            placeholder='Enter identity name or ID'
        )
    ], style={'display': 'flex', 'align-items': 'center', 'margin-top': '20px'}),
    html.Div(id='search-results', children=[
        html.H2('Search Results'),
        html.Div(id='identity-list')
    ])
])

@app.callback(
    Output('search-results', 'children'),
    Input('submit-button', 'n_clicks'),
    Input('identity-search', 'value'),
    State('tenant-input', 'value'),
    State('client-id-input', 'value'),
    State('client-secret-input', 'value')
)
def search_identity(n_clicks, identity_search, tenant, client_id, client_secret):
    if n_clicks == 0 or not tenant or not client_id or not client_secret:
        return ''

    if not hasattr(app, 'token'):
        try:
            # Retrieve OAuth token
            token_url = f'https://{tenant}.api.identitynow-demo.com/oauth/token'
            data = {
                'grant_type': 'client_credentials',
                'client_id': client_id,
                'client_secret': client_secret
            }
            response = requests.post(token_url, data=data)
            response.raise_for_status()  # Raise an exception for 4xx or 5xx status codes
            token = response.json()['access_token']
            #print(f"Retrieved token: {token}")
        except requests.exceptions.RequestException as e:
            print(f"Error retrieving token: {e}")
            return html.Div(f"Error retrieving token: {str(e)}")

    if not identity_search:
        return ''

    try:
        # Make API call to search for identity
        url = f'https://{tenant}.api.identitynow-demo.com/v3/search'
        print(f"Identity search URL: {url}")  # Debug: Log the full URL

        headers = {
            'Content-Type': 'application/json',
            'Accept': 'application/json',
            'Authorization': f'Bearer {token}'
        }

        payload = json.dumps({
            "indices": ["identities"],
            "query": {
                "query": f"attributes.displayName: {identity_search}"
            },
            "sort": ["displayName"]
        })

        response = requests.request('POST', url, headers=headers, data=payload)

        try:
            response.raise_for_status()  # Raise an exception for 4xx or 5xx status codes
        except requests.exceptions.RequestException as e:
            error_details = response.json()
            print(f"Error making API call: {e}")
            print(f"Error details: {error_details}")  # Debug: Log the error details
            return html.Div(f"Error making API call: {str(e)}")

        # Prepare the results
        result = []
        identities = response.json()
        print(response.status_code)
        print(identities)

        for identity in identities:
            identity_div = [
                html.H3(identity['displayName']),
                html.Div([
                    html.Strong('ID: '),
                    html.Span(identity['id'])
                ]),
                html.Div([
                    html.Strong('Attributes:')
                ]),
                html.Ul([
                    html.Li(f"{attr.capitalize()}: {str(value)}")
                    for attr, value in identity.get('attributes', {}).items()
                ]),
                html.Div([
                    html.Strong('Accounts:')
                ]),
                html.Ul([
                    html.Li([
                        html.Div(f"Account ID: {account['accountId']}"),
                        html.Div(f"Source: {account['source']['name']} ({account['source']['type']})"),
                        html.Div(f"Disabled: {account['disabled']}"),
                        html.Div(f"Locked: {account['locked']}"),
                        html.Div(f"Privileged: {account['privileged']}"),
                        html.Div(f"Manually Correlated: {account['manuallyCorrelated']}"),
                        html.Div(f"Created: {account['created']}")
                    ])
                    for account in identity.get('accounts', [])
                ])
            ]

            for attr, value in identity.items():
                if attr not in ['id', 'displayName', 'attributes', 'accounts']:
                    identity_div.append(html.Div([
                        html.Strong(f"{attr.capitalize()}: "),
                        html.Span(str(value))
                    ]))

            identity_div.append(html.Hr())
            result.append(html.Div(identity_div))

        return result

    except requests.exceptions.RequestException as e:
        print(f"Error making API call: {e}")
        return [html.Div(f"Error making API call: {str(e)}")]

if __name__ == '__main__':
    app.run_server(debug=True)

The app when executed will listen on port 8050 by default. Open a browser, specify the tenant name, Client ID, and Client Secret. The search field would require a user’s name.

Note that the code is hard coded for the api URL to be {tenant}.api.identitynow-demo.com. You will want to remove the -demo part of the URL if it is not a dev or partner tenant.

1 Like