Open Closed

Use case: how to implement Impersonation "Login with this use" and Authority delegation "Login" buttons on custom page #6956


User avatar
0
viktor created
  • ABP Commercial version: v8.0.5
  • UI Type: Angular
  • Database System: EF Core (SQLite)
  • Tiered (for MVC) or Auth Server Separated (for Angular): no

We want to implement custom page (Angular) to provide list of users and also button with possibility to "Log in with this user" or "Login" if user has delegates. Functionality should be same way as on Angular Lepton X.

We need this button on custom page

And this button if delegates exists:

How to implement functionality of such buttons? May you provide steps and examples of code for Angular.


15 Answer(s)
  • User Avatar
    0
    viktor created

    Any news so far?

  • User Avatar
    0
    Sturla created

    Can you use something from here? https://support.abp.io/qa/questions/6852/3a115e97-a34f-d9c6-6848-7adc94262fc1

  • User Avatar
    0
    viktor created

    Hi, @Sturla I can not lilnk to my use case this issue. It is not Angular. May you provide how can I call "Login with this user" from Angular side?

  • User Avatar
    0
    sumeyye.kurtulus created
    Support Team Angular Developer

    Hello Viktor, both Login with this user and Login buttons use impersonation technique either by sending the user id or the delegated user id.

    In order to customize and manage these functionalities, we recommend you to use the functions inside ImpersonationService (https://docs.abp.io/en/commercial/latest/modules/account/impersonation#angular).

    If you need further customization, you can also utilize the loginUsingGrant function inside the AuthService Because, this function handles the request by getting the parameter object that would contain the necessary id and a grant type like Impersonation. (https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/abstracts/auth.service.ts)

  • User Avatar
    0
    viktor created

    Hi, @sumeyye.kurtulus Thank you for your replay and links to documents but I still have some doubts. May you provide simple code/flow how we should use all of this.

    Let's assume I have created custom component with list of users on my custom Angular page. For some users I can put button "Login" because (I have custom Controller and get users I have delagations about). So I did login and I see list users: Example: User 1 > "Login" User 2 > "No permissions"

    May you put example of code for "Login" button event (Angular side)?

  • User Avatar
    0
    masum.ulu created
    Support Team Angular Developer

    Hi viktor,

    As far as I understand you want to

    • List users in custom page
    • If current user have permission you want to display name like Login or Login with this user option button in actions dropdown button. Like below

    • After click this button you want to open a modal if selected user's have authority delegations
    • After that you want to use casual authority delegation system

    Is that right ?

    If it's yes, Our current authority delegation system works for CurrentUser which means if you want use authority delegation system for selected user, you need to apply your custom logic at backend. So you also need to create new proxy (DTOs and Services). So the last step is the display authority delegation modal for selected user.

    Or maybe I couldn't understand your goal. Can you correct me please ?

  • User Avatar
    0
    viktor created

    Hi, @masum.ulu We do not want to implement anything sprecific. We just want to have "Login" or "Login with this user" buttons as stand alone buttons - out from "Actions" drop down. I can not find any examples hot to implement (click) action on button.

    We have implemented saparate page with list of delegated users for CurrentUser. And we want to add button "Login", but we do not know how to properly implement (click) event for this button.

  • User Avatar
    0
    viktor created

    Hi, Any news so far? It is already 11d. past and we still awaiting for simple code example.

  • User Avatar
    0
    sumeyye.kurtulus created
    Support Team Angular Developer

    Hello again Viktor, sorry for the delay caused by the chain of misunderstandings. You can customize the entity actions for your page. Here is the default config for the identity module actions:

    export const DEFAULT_USERS_ENTITY_ACTIONS = EntityAction.createMany<IdentityUserDto>([
      {
        text: 'AbpIdentity::ViewDetails',
        action: data => {
          const component = data.getInjected(UsersComponent);
          component.viewDetails(data.record);
        },
        permission: 'AbpIdentity.Users.ViewDetails',
      },
      {
        text: 'AbpUi::Edit',
        action: data => {
          const component = data.getInjected(UsersComponent);
          component.onEdit(data.record.id);
        },
        permission: 'AbpIdentity.Users.Update',
      },
      {
        text: 'AbpIdentity::Claims',
        action: data => {
          const component = data.getInjected(UsersComponent);
          component.onManageClaims(data.record.id);
        },
        permission: 'AbpIdentity.Users.Update',
      },
      {
        text: 'AbpIdentity::Lock',
        action: data => {
          const component = data.getInjected(UsersComponent);
          component.selected = data.record;
          component.isLockModalVisible = true;
        },
        permission: 'AbpIdentity.Users.Update',
        visible: data => {
          const configState = data.getInjected(ConfigStateService);
          const currentUserId = configState.getDeep('currentUser.id');
    
          return (
            data.record.id !== currentUserId && data.record.lockoutEnabled
          );
        },
      },
      {
        text: 'AbpIdentity::Unlock',
        action: data => {
          const component = data.getInjected(UsersComponent);
          component.unlock(data.record.id);
        },
        permission: 'AbpIdentity.Users.Update',
        visible: data => data.record.isLockedOut,
      },
      {
        text: 'AbpIdentity::Permissions',
        action: data => {
          const component = data.getInjected(UsersComponent);
          component.openPermissionsModal(data.record.id, data.record.userName);
        },
        permission: 'AbpIdentity.Users.ManagePermissions',
      },
      {
        text: 'AbpIdentity::ChangeHistory',
        action: data => {
          const showHistory = data.getInjected(SHOW_ENTITY_HISTORY);
          showHistory(data.record.id, 'Volo.Abp.Identity.IdentityUser');
        },
        permission: 'AuditLogging.ViewChangeHistory:Volo.Abp.Identity.IdentityUser',
        visible: data => Boolean(data.getInjected(SHOW_ENTITY_HISTORY, null)),
      },
      {
        text: 'AbpIdentity::SetPassword',
        action: data => {
          const component = data.getInjected(UsersComponent);
          component.selected = data.record;
          component.isSetPasswordModalVisible = true;
        },
        permission: 'AbpIdentity.Users.Update',
      },
      {
        text: 'AbpIdentity::TwoFactor',
        action: data => {
          const component = data.getInjected(UsersComponent);
          component.selected = data.record;
          component.service.getTwoFactorEnabled(data.record.id).subscribe(res => {
            component.twoFactor.checkboxValue = res;
            component.twoFactor.isModalVisible = true;
          });
        },
        permission: 'AbpIdentity.Users.Update',
        visible: data => data.getInjected(UsersComponent).twoFactor.isOptional,
      },
      {
        text: 'AbpIdentity::LoginWithThisUser',
        permission: 'AbpIdentity.Users.Impersonation',
        action: data => {
          const impersonation = data.getInjected(ImpersonationService);
          impersonation.impersonateUser(data.record.id).subscribe();
        },
        visible: data => {
          const configState = data.getInjected(ConfigStateService);
          const currentUserId = configState.getDeep('currentUser.id');
          const currentImpersonatorUserId = configState.getDeep('currentUser.impersonatorUserId');
          return data.record.id !== currentUserId && currentImpersonatorUserId === null;
        },
      },
      {
        text: 'AbpUi::Delete',
        action: data => {
          const component = data.getInjected(UsersComponent);
          component.delete(data.record.id, data.record.name || data.record.userName);
        },
        permission: 'AbpIdentity.Users.Delete',
      },
    ]);
    

    In this case, you will need to customize the entity actions to manage this. Here is the related documentation that explains the procedure step by step

    https://docs.abp.io/en/abp/latest/UI/Angular/Entity-Action-Extensions

  • User Avatar
    0
    viktor created

    Hello again Viktor, sorry for the delay caused by the chain of misunderstandings. You can customize the entity actions for your page. Here is the default config for the identity module actions:

    export const DEFAULT_USERS_ENTITY_ACTIONS = EntityAction.createMany<IdentityUserDto>([ 
      { 
        text: 'AbpIdentity::ViewDetails', 
        action: data => { 
          const component = data.getInjected(UsersComponent); 
          component.viewDetails(data.record); 
        }, 
        permission: 'AbpIdentity.Users.ViewDetails', 
      }, 
      { 
        text: 'AbpUi::Edit', 
        action: data => { 
          const component = data.getInjected(UsersComponent); 
          component.onEdit(data.record.id); 
        }, 
        permission: 'AbpIdentity.Users.Update', 
      }, 
      { 
        text: 'AbpIdentity::Claims', 
        action: data => { 
          const component = data.getInjected(UsersComponent); 
          component.onManageClaims(data.record.id); 
        }, 
        permission: 'AbpIdentity.Users.Update', 
      }, 
      { 
        text: 'AbpIdentity::Lock', 
        action: data => { 
          const component = data.getInjected(UsersComponent); 
          component.selected = data.record; 
          component.isLockModalVisible = true; 
        }, 
        permission: 'AbpIdentity.Users.Update', 
        visible: data => { 
          const configState = data.getInjected(ConfigStateService); 
          const currentUserId = configState.getDeep('currentUser.id'); 
     
          return ( 
            data.record.id !== currentUserId && data.record.lockoutEnabled 
          ); 
        }, 
      }, 
      { 
        text: 'AbpIdentity::Unlock', 
        action: data => { 
          const component = data.getInjected(UsersComponent); 
          component.unlock(data.record.id); 
        }, 
        permission: 'AbpIdentity.Users.Update', 
        visible: data => data.record.isLockedOut, 
      }, 
      { 
        text: 'AbpIdentity::Permissions', 
        action: data => { 
          const component = data.getInjected(UsersComponent); 
          component.openPermissionsModal(data.record.id, data.record.userName); 
        }, 
        permission: 'AbpIdentity.Users.ManagePermissions', 
      }, 
      { 
        text: 'AbpIdentity::ChangeHistory', 
        action: data => { 
          const showHistory = data.getInjected(SHOW_ENTITY_HISTORY); 
          showHistory(data.record.id, 'Volo.Abp.Identity.IdentityUser'); 
        }, 
        permission: 'AuditLogging.ViewChangeHistory:Volo.Abp.Identity.IdentityUser', 
        visible: data => Boolean(data.getInjected(SHOW_ENTITY_HISTORY, null)), 
      }, 
      { 
        text: 'AbpIdentity::SetPassword', 
        action: data => { 
          const component = data.getInjected(UsersComponent); 
          component.selected = data.record; 
          component.isSetPasswordModalVisible = true; 
        }, 
        permission: 'AbpIdentity.Users.Update', 
      }, 
      { 
        text: 'AbpIdentity::TwoFactor', 
        action: data => { 
          const component = data.getInjected(UsersComponent); 
          component.selected = data.record; 
          component.service.getTwoFactorEnabled(data.record.id).subscribe(res => { 
            component.twoFactor.checkboxValue = res; 
            component.twoFactor.isModalVisible = true; 
          }); 
        }, 
        permission: 'AbpIdentity.Users.Update', 
        visible: data => data.getInjected(UsersComponent).twoFactor.isOptional, 
      }, 
      { 
        text: 'AbpIdentity::LoginWithThisUser', 
        permission: 'AbpIdentity.Users.Impersonation', 
        action: data => { 
          const impersonation = data.getInjected(ImpersonationService); 
          impersonation.impersonateUser(data.record.id).subscribe(); 
        }, 
        visible: data => { 
          const configState = data.getInjected(ConfigStateService); 
          const currentUserId = configState.getDeep('currentUser.id'); 
          const currentImpersonatorUserId = configState.getDeep('currentUser.impersonatorUserId'); 
          return data.record.id !== currentUserId && currentImpersonatorUserId === null; 
        }, 
      }, 
      { 
        text: 'AbpUi::Delete', 
        action: data => { 
          const component = data.getInjected(UsersComponent); 
          component.delete(data.record.id, data.record.name || data.record.userName); 
        }, 
        permission: 'AbpIdentity.Users.Delete', 
      }, 
    ]); 
    

    In this case, you will need to customize the entity actions to manage this. Here is the related documentation that explains the procedure step by step

    https://docs.abp.io/en/abp/latest/UI/Angular/Entity-Action-Extensions

    This did not help to understand how to code action.

    Let me try to ask in different way. We have created following page on Angular (see picture below). And we know CurrentUser have delegation to "Login" as John (there is Login button below name John marked with red arrow). So I need to know how to code (click) action/event of this. After Click it will Login (Impersonate) as John. And also we need to have button "Back to CurrentUser" and also have action to get back.

  • User Avatar
    0
    viktor created

    Hi, We are blocked with next steps in implementation of solution for our customer. May someone check last comment and provide code example?

  • User Avatar
    0
    sumeyye.kurtulus created
    Support Team Angular Developer

    Hello again Viktor. As I have emphasized before you can use the ImpersonationService functionalities by constructing a code block like this:

      protected impersonationService = inject(ImpersonationService);
      protected permissionService = inject(PermissionService);
    
    	//This is the part you implement as a click event
      loginWithThisUser(user) {
        const canImpersonate = this.permissionService.getGrantedPolicy(
          'AbpIdentity.Users.Impersonation',
        );
        const currentUserId = this.configState.getDeep('currentUser.id');
        const currentImpersonatedUserId = this.configState.getDeep('currrentUser.impersontorUserId');
    
        const { id: userId } = user.record;
        const isCurrentUser = userId === currentUserId;
        const isImpersonating = currentImpersonatedUserId !== undefined;
    
        if (isCurrentUser || isImpersonating || !canImpersonate) {
          return;
        }
        this.impersonationService.impersonateUser(user.record.id).subscribe();
      }
    

    Here in this part, you need to check three important points for a successful login and redirect

    1. Impersonation permission
    2. Current user which cannot be impersonated
    3. If the current user already have an impersonated user

    Since then this is the basic conditions, you can customize it for your case.

    Based on your condition, you can check whether this user is already impersonated you can change the Login button as Go Back to My Account . Here is how you can manage this:

    protected impersonationService = inject(ImpersonationService);
    
    goBackToMyAccount(){
    	 this.impersonation.impersonate({}).subscribe();
    }
    
  • User Avatar
    0
    viktor created

    Hi, @sumeyye.kurtulus this looks ok if it related to Impersotaion. If user have permission so it will work. But we want to use Delegation.

    From my example above (picture) I have CurrentUser and it allowed to "Login" based on Delegation from user John. What code for "Login" action (click) should be in such scenario? Looks to security issue.

  • User Avatar
    0
    viktor created

    Hi, @sumeyye.kurtulus Please check my last comment as we need help on this.

    And one more **important **question: Is it any validation exists on backend services - looks like we may Impersonate any-to-any user even for those which has no Impersonate permissions assigned.

  • User Avatar
    0
    sumeyye.kurtulus created
    Support Team Angular Developer

    Hello Viktor, again. Normally, if the user has no impersonation permission, you need to receive an error saying that "Require AbpIdentity.Users.Impersonation permission to impersonate user!" This applies for logging in using the specific account without using authority delegation.

    However, if you want to use authority delegation, you need to give the delegation for that specific user, and here is how we can do it

    After you list the delegated user. You can use such an approach

     protected readonly configState = inject(ConfigStateService);
     protected readonly authService = inject(AuthService);
     protected readonly environment = inject(EnvironmentService);
    
      login(userDelegationId: string) {
        const impersonatedUser = this.configState.getDeep('currentUser.impersonatorUserId');
        const impersonatedTenant = this.configState.getDeep('currentUser.impersonatorTenantId');
        if (impersonatedTenant || impersonatedUser) {
          return;
        }
        this.environment.getEnvironment$().pipe(
          tap(({ oAuthConfig: { responseType } }) => {
            if (responseType === 'code') {
              this.authService.oidc = false;
            }
          }),
          switchMap(() => {
            const promiseOfLogin = this.authService.loginUsingGrant('Impersonation', {
              UserDelegationId: userDelegationId,
            });
            return from(promiseOfLogin).pipe(
              tap(
                () => (location.href = this.environment.getEnvironment().application?.baseUrl || '/'),
              ),
            );
          }),
        ).subscribe();
      }
    
Made with ❤️ on ABP v8.2.0-preview Updated on March 25, 2024, 15:11