diff --git a/docs/tutorials/articles/user_management/index.md b/docs/tutorials/articles/user_management/index.md index f6d049bf2..feb4fa85c 100644 --- a/docs/tutorials/articles/user_management/index.md +++ b/docs/tutorials/articles/user_management/index.md @@ -9,9 +9,6 @@ hide: - toc --- - -Adding User Management to your Taipy application using Taipy Enterprise is a smooth experience — integration is intuitive, and just makes sense. This is because Taipy was designed from the beginning to be paired with Taipy Enterprise for secure and easy-to-develop User Management. - !!! note "Taipy Enterprise edition" Taipy provides robust, business-focused applications tailored for enterprise @@ -24,9 +21,11 @@ Adding User Management to your Taipy application using Taipy Enterprise is a smo [Try it live](https://investment-screening.taipy.cloud){: .tp-btn target='blank' } [Contact us](https://taipy.io/book-a-call){: .tp-btn .tp-btn--accent target='blank' } +Adding User Management to your Taipy application using Taipy Enterprise is a smooth experience — integration is intuitive, and just makes sense. This is because Taipy was from the beginning to be paired with Taipy Enterprise for secure and easy-to-develop User Management. + ![Investment Screening application.](images/company_page_alice.png){width=80% : .tp-image-border} -In this tutorial, we will create an application for an investment firm use case. This investment firm is testing out a **new screening model to evaluate companies** that they should invest in. This comprehensive screening model is **expensive to run** because it requires access to paid data sources — accordingly, our application’s purpose is to: +In this tutorial, we will create an application for an investment firm use case. This investment firm is testing out a **new screening model to evaluate companies** that they should invest in. This comprehensive screening model is **expensive to run** because it requires access to paid data sources — accordingly, our application's purpose is to: 1. Allow Bob, the **analyst**, to add companies that look promising for investment; and 2. Allow Alice, the **manager**, to submit companies to the screening model. @@ -38,14 +37,14 @@ In developing this multi-page application, we will demonstrate these features of 1. **Permissions** for **Scenario and Data Management** using [**predefined roles**](../../../userman/advanced_features/auth/authorization.md#permissions-for-scenario-and-data-management) ("TAIPY_EDITOR", "TAIPY_ADMIN", etc.); 2. **Authorizing** that a user possesses **specified roles** using a [*RoleTraits*](../../../userman/advanced_features/auth/authorization.md#role-traits-value) filter (*AnyOf*, *AllOf* and *NoneOf*); 3. Managing **role-based page access** using [*AuthorizedPage*](https://docs.taipy.io/en/latest/refmans/reference/pkg_taipy/pkg_enterprise/pkg_gui/AuthorizedPage/); -4. **Hiding elements** based on the user’s roles; +4. **Hiding elements** based on the user's roles; 5. And more. This tutorial focuses on Taipy Enterprise features, and assumes some understanding of Taipy GUI and Scenario Management concepts. Please consult the useful User Manual and Tutorials if you have trouble understanding the latter! # 1. Folder structure -First off, let’s take a look at the final folder structure that we’ll end up with: +First off, let's take a look at the final folder structure that we'll end up with: ``` src/ @@ -68,7 +67,7 @@ src/ # 2. Business logic -We’ll start with the business logic and scenario management. +We'll start with the business logic and scenario management. First, we have the screening model algorithm: @@ -127,9 +126,9 @@ In our first Taipy Enterprise feature, we will define the Taipy authentication a Note that Taipy Enterprise supports several authentication protocols which you can check out [here](../../../userman/advanced_features/auth/authentication.md). To properly secure your application, you **should use an established supported protoco**l like LDAP or Microsoft Entra ID. However, for our purpose, we will use an **internal password-based protocol** simply called "taipy". This lets us get started quickly, before **migrating to one of the aforementioned protocols** at a later time. -First, Taipy’s "taipy" protocol expects a "*TAIPY_AUTH_HASH*" environment variable used to [salt the password](https://en.wikipedia.org/wiki/Salt_(cryptography)) before it is hashed. +First, Taipy's "taipy" protocol expects a "*TAIPY_AUTH_HASH*" environment variable used to [salt the password](https://en.wikipedia.org/wiki/Salt_(cryptography)) before it is hashed. -We’ll do this in an "*.env*" file: +We'll do this in an "*.env*" file: ```toml # .env @@ -177,21 +176,21 @@ We perform the configuration with the `Config.configure_authentication` method. A dictionary mapping usernames to their hashed password string. Password hashes should be generated using `taipy.auth.hash_taipy_password`. - Notice that we did not provide an entry for Alice’s password. By default, if omitted, the password hash is automatically generated with the username as the password. In other words, we can sign in as the user "Alice" with the password "Alice". + Notice that we did not provide an entry for Alice's password. By default, if omitted, the password hash is automatically generated with the username as the password. In other words, we can sign in as the user "Alice" with the password "Alice". # 4. Login page -We’re down to our final 4 files, and we’ll start with the login page. This is where things get the most interesting, and it’s worth spending some time to understand what’s going on. +We're down to our final 4 files, and we'll start with the login page. This is where things get the most interesting, and it's worth spending some time to understand what's going on. A user can be identified through a [`taipy.auth.Credentials`](https://docs.taipy.io/en/latest/refmans/reference/pkg_taipy/pkg_auth/Credentials/) object which holds information about its username and roles. We typically obtain an **authenticated Credentials object** using: 1. [`taipy.auth.login`](https://docs.taipy.io/en/latest/refmans/reference/pkg_taipy/pkg_auth/login/); or 2. [`taipy.enterprise.gui.login`](https://docs.taipy.io/en/latest/refmans/reference/pkg_taipy/pkg_enterprise/pkg_gui/login/). -Although they both return valid Credentials objects (or raise errors), the **latter function** differs in that it also **performs some operations behind-the-scenes** to enable **interoperability with Taipy GUI** (e.g. *AuthorizedPage*, described in a later section). Consequently, we’ll be using the latter function for our application. +Although they both return valid Credentials objects (or raise errors), the **latter function** differs in that it also **performs some operations behind-the-scenes** to enable **interoperability with Taipy GUI** (e.g. *AuthorizedPage*, described in a later section). Consequently, we'll be using the latter function for our application. -Let’s look at the code for the login page: +Let's look at the code for the login page: ```python # pages/login.py @@ -226,7 +225,7 @@ with tgb.Page() as login_page: ``` -We don’t need much code for this page. The actual Page object (`login_page`) contains a single element: a [login control](../../../refmans/gui/viselements/generic/login.md). Interacting with the login control implicitly calls the "on_login" callback function (unless a callback function is explicitly assigned via `tgb.login`’s "on_action" parameter). +We don't need much code for this page. The actual Page object (`login_page`) contains a single element: a [login control](../../../refmans/gui/viselements/generic/login.md). Interacting with the login control implicitly calls the "on_login" callback function (unless a callback function is explicitly assigned via `tgb.login`'s "on_action" parameter). In turn, our "on_login" callback function calls "handle_login", where we implement the main logic for attempting a login. We choose to do this in 3 steps: @@ -253,7 +252,7 @@ Similarly, we define a "handle_logout" function: 2. `state.credentials = get_guest_credentials()`. - Here, we’re assigning a "blank" Credentials object to `state.credentials`, which has no user roles. This is for our convenience so that we can always use this `state.credentials` object to get a user’s roles — returning an empty list if the user is not authenticated. + Here, we're assigning a "blank" Credentials object to `state.credentials`, which has no user roles. This is for our convenience so that we can always use this `state.credentials` object to get a user's roles — returning an empty list if the user is not authenticated. It is an important distinction that `state.credentials` is a state variable of our own making — **it is not some reserved keyword** internally used by Taipy Enterprise. Notably and contrastingly, at this point, calling [`taipy.enterprise.gui.get_credentials`](https://docs.taipy.io/en/latest/refmans/reference/pkg_taipy/pkg_enterprise/pkg_gui/get_credentials/) would return None, since the previous logout statement has removed session credentials. @@ -262,7 +261,7 @@ Similarly, we define a "handle_logout" function: # 5. Admin page -Taking a quick break from the fancier stuff, let’s make a simple page: +Taking a quick break from the fancier stuff, let's make a simple page: ```python # pages/admin.py @@ -327,7 +326,7 @@ with tgb.Page() as company_page: ``` -If you’re already familiar with Taipy GUI and Scenario Management, you will notice that we used some [scenario management controls](../../../refmans/gui/viselements/index.md#scenario-and-data-management-controls), namely: *scenario_selector*, *scenario*, *data_node_selector* and *data_node*. +If you're already familiar with Taipy GUI and Scenario Management, you will notice that we used some [scenario management controls](../../../refmans/gui/viselements/index.md#scenario-and-data-management-controls), namely: *scenario_selector*, *scenario*, *data_node_selector* and *data_node*. In fact, the only new thing present is the *AnyOf* [role trait](../../../userman/advanced_features/auth/authorization.md#role-traits) filter. A role trait filter takes 3 parameters: @@ -337,14 +336,14 @@ In fact, the only new thing present is the *AnyOf* [role trait](../../../userman Additionally, *success* and *failure* could also be role trait filters, which would be **recursively evaluated** until some value is returned. -Rather self-explanatorily, the *AnyOf* role traits filter checks if a user has any of the listed roles. In our example, we created one like so: `is_admin = AnyOf([TAIPY_ADMIN_ROLE], True, False)`. Then, in our page code, we used the expression `not is_admin.get_traits(credentials)` for a part’s render property. Hence, the full expression (with the negation from `not`) evaluates as `True` if the user does not have the "TAIPY_ADMIN" role. +Rather self-explanatorily, the *AnyOf* role traits filter checks if a user has any of the listed roles. In our example, we created one like so: `is_admin = AnyOf([TAIPY_ADMIN_ROLE], True, False)`. Then, in our page code, we used the expression `not is_admin.get_traits(credentials)` for a part's render property. Hence, the full expression (with the negation from `not`) evaluates as `True` if the user does not have the "TAIPY_ADMIN" role. ![Bob is unable to click the button as he has insufficient permissions to submit the scenario.](images/company_page_bob.png){width=80% : .tp-image-border} /// caption Company page: Bob is unable to click the button as he has insufficient permissions to submit the scenario. /// -Note that using scenario management controls abstracts away some auth functionality for our convenience. For example, the *scenario* control’s submit button is **automatically disabled** when the **user does not have permission** to execute a scenario. Often, you may wish to use generic controls like selectors and buttons, which trigger user-defined callbacks that call scenario management functionality. In this case, an added step is to use the [Authorize](https://docs.taipy.io/en/latest/refmans/reference/pkg_taipy/pkg_auth/Authorize/) context manager when performing a protected operation, for example: +Note that using scenario management controls abstracts away some auth functionality for our convenience. For example, the *scenario* control's submit button is **automatically disabled** when the **user does not have permission** to execute a scenario. Often, you may wish to use generic controls like selectors and buttons, which trigger user-defined callbacks that call scenario management functionality. In this case, an added step is to use the [Authorize](https://docs.taipy.io/en/latest/refmans/reference/pkg_taipy/pkg_auth/Authorize/) context manager when performing a protected operation, for example: ```python import taipy as tp @@ -446,11 +445,11 @@ if __name__ == "__main__": ``` -Since this file is a little bit longer than the others, we’ll break it down in sections: +Since this file is a little bit longer than the others, we'll break it down in sections: ## 7.1 Initialize credentials -Starting from the top, after the imports, we initialize `credentials = get_guest_credentials()`. By defining this in our main module, we now have a global `state.credentials` state variable in our application. As mentioned earlier, we’re giving non-logged in users this "blank" guest credentials, so that `state.credentials` is always of type *Credentials* — and guest users can be identified by their empty role list. +Starting from the top, after the imports, we initialize `credentials = get_guest_credentials()`. By defining this in our main module, we now have a global `state.credentials` state variable in our application. As mentioned earlier, we're giving non-logged in users this "blank" guest credentials, so that `state.credentials` is always of type *Credentials* — and guest users can be identified by their empty role list. ## 7.2 "on_exception" global callback @@ -462,9 +461,9 @@ Remember how in "login.handle_login" we opted not to handle the exception that ` We usually create a multi-page application in Taipy by defining a dictionary mapping page names to a Taipy *Page* (like `company_page`). This dictionary is then passed to the `taipy.gui.Gui` "pages" parameter. -This time however, we may want to show different pages depending on the user’s roles. For example, we only want Alice, the manager (who has the "TAIPY_ADMIN" role), to be able to view `admin_page`. Any user without this role should not be able to view the page. +This time however, we may want to show different pages depending on the user's roles. For example, we only want Alice, the manager (who has the "TAIPY_ADMIN" role), to be able to view `admin_page`. Any user without this role should not be able to view the page. -To do this, we started by creating `guest_page`, a short page simply informing the user that they aren’t signed in. Now, we want to make it so that navigating to "/admin" may conditionally show `admin_page` or `guest_page`, depending on the user’s roles. We can do this with *AuthorizedPage*. +To do this, we started by creating `guest_page`, a short page simply informing the user that they aren't signed in. Now, we want to make it so that navigating to "/admin" may conditionally show `admin_page` or `guest_page`, depending on the user's roles. We can do this with *AuthorizedPage*. See the following snippet: @@ -505,7 +504,7 @@ Bob, the **analyst**, can now create a scenario, and add a company name that he ![Bob edits the company name in the scenario.](images/bob_edits_company_name.png){width=80% : .tp-image-border} -Alice, the **manager**, will see the scenario that Bob created, and can decide if it’s worth submitting to the screening model. She **has permission** to submit the scenario for execution: +Alice, the **manager**, will see the scenario that Bob created, and can decide if it's worth submitting to the screening model. She **has permission** to submit the scenario for execution: ![Alice submits the scenario.](images/alice_submits_scenario.png){width=80% : .tp-image-border}