Merge branch 'dev' into blazor-ui

pull/5399/head
Halil İbrahim Kalkan 5 years ago
commit fbe14bfb01

@ -4,3 +4,26 @@ Identity module is used to manage [organization units](Organization-Units.md), r
See [the source code](https://github.com/abpframework/abp/tree/dev/modules/identity). Documentation will come soon...
## Identity Security Log
The security log can record some important operations or changes about your account. You can save the security log if needed.
You can inject and use `IdentitySecurityLogManager` or `ISecurityLogManager` to write security logs. It will create a log object by default and fill in some common values, such as `CreationTime`, `ClientIpAddress`, `BrowserInfo`, `current user/tenant`, etc. Of course, you can override them.
```cs
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
{
Identity = "IdentityServer";
Action = "ChangePassword";
});
```
Configure `AbpSecurityLogOptions` to provide the application name for the log or disable this feature. **Enabled** by default.
```cs
Configure<AbpSecurityLogOptions>(options =>
{
options.ApplicationName = "AbpSecurityTest";
});
```

@ -1,5 +1,28 @@
# 身份管理模块
身份模块基于Microsoft Identity 库用于管理[组织单元](Organization-Units.md), 角色, 用户和他们的权限.
身份模块基于Microsoft Identity库用于管理[组织单元](Organization-Units.md), 角色, 用户和他们的权限.
参阅 [源码](https://github.com/abpframework/abp/tree/dev/modules/identity). 文档很快会被完善.
参阅 [源码](https://github.com/abpframework/abp/tree/dev/modules/identity). 文档很快会被完善.
## Identity安全日志
安全日志可以记录账户的一些重要的操作或者改动, 你可以在在一些功能中保存安全日志.
你可以注入和使用 `IdentitySecurityLogManager``ISecurityLogManager` 来保存安全日志. 默认它会创建一个安全日志对象并填充常用的值. 如 `CreationTime`, `ClientIpAddress`, `BrowserInfo`, `current user/tenant`等等. 当然你可以自定义这些值.
```cs
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
{
Identity = "IdentityServer";
Action = "ChangePassword";
});
```
通过配置 `AbpSecurityLogOptions` 来提供应用程序的名称或者禁用安全日志功能. 默认是**启用**状态.
```cs
Configure<AbpSecurityLogOptions>(options =>
{
options.ApplicationName = "AbpSecurityTest";
});
```

@ -28,9 +28,11 @@ namespace Volo.Abp.Identity
);
Task<List<OrganizationUnit>> GetListAsync(
Guid? parentId,
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
string filter = null,
bool includeDetails = false,
CancellationToken cancellationToken = default
);
@ -112,5 +114,10 @@ namespace Volo.Abp.Identity
OrganizationUnit organizationUnit,
CancellationToken cancellationToken = default
);
Task<long> GetLongCountAsync(
Guid? parentId,
string filter = null,
CancellationToken cancellationToken = default);
}
}

@ -1,6 +1,7 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq.Dynamic.Core;
using System.Linq;
using System.Threading;
@ -44,14 +45,20 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
}
public virtual async Task<List<OrganizationUnit>> GetListAsync(
Guid? parentId,
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
string filter = null,
bool includeDetails = true,
CancellationToken cancellationToken = default)
{
return await DbSet
.IncludeDetails(includeDetails)
.Where(ou=>ou.ParentId==parentId)
.WhereIf(!filter.IsNullOrWhiteSpace(),
ou => ou.DisplayName.Contains(filter) ||
ou.Code.Contains(filter))
.OrderBy(sorting ?? nameof(OrganizationUnit.DisplayName))
.PageBy(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
@ -90,9 +97,9 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
CancellationToken cancellationToken = default)
{
var query = from organizationRole in DbContext.Set<OrganizationUnitRole>()
join role in DbContext.Roles.IncludeDetails(includeDetails) on organizationRole.RoleId equals role.Id
where organizationRole.OrganizationUnitId == organizationUnit.Id
select role;
join role in DbContext.Roles.IncludeDetails(includeDetails) on organizationRole.RoleId equals role.Id
where organizationRole.OrganizationUnitId == organizationUnit.Id
select role;
query = query
.OrderBy(sorting ?? nameof(IdentityRole.Name))
.PageBy(skipCount, maxResultCount);
@ -105,9 +112,9 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
CancellationToken cancellationToken = default)
{
var query = from organizationRole in DbContext.Set<OrganizationUnitRole>()
join role in DbContext.Roles on organizationRole.RoleId equals role.Id
where organizationRole.OrganizationUnitId == organizationUnit.Id
select role;
join role in DbContext.Roles on organizationRole.RoleId equals role.Id
where organizationRole.OrganizationUnitId == organizationUnit.Id
select role;
return await query.CountAsync(GetCancellationToken(cancellationToken));
}
@ -157,8 +164,8 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
var query = CreateGetMembersFilteredQuery(organizationUnit, filter);
return await query.IncludeDetails(includeDetails).OrderBy(sorting ?? nameof(IdentityUser.UserName))
.PageBy(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
.PageBy(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<int> GetMembersCountAsync(
@ -245,7 +252,19 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
DbContext.Set<IdentityUserOrganizationUnit>().RemoveRange(ouMembersQuery);
}
protected virtual IQueryable<IdentityUser> CreateGetMembersFilteredQuery(OrganizationUnit organizationUnit, string filter = null)
public virtual async Task<long> GetLongCountAsync(Guid? parentId, string filter = null,
CancellationToken cancellationToken = default)
{
return await DbSet
.Where(ou=>ou.ParentId==parentId)
.WhereIf(!filter.IsNullOrWhiteSpace(), ou =>
ou.DisplayName.Contains(filter) ||
ou.Code.Contains(filter))
.LongCountAsync(GetCancellationToken(cancellationToken));
}
protected virtual IQueryable<IdentityUser> CreateGetMembersFilteredQuery(OrganizationUnit organizationUnit,
string filter = null)
{
var query = from userOu in DbContext.Set<IdentityUserOrganizationUnit>()
join user in DbContext.Users on userOu.UserId equals user.Id
@ -264,4 +283,4 @@ namespace Volo.Abp.Identity.EntityFrameworkCore
return query;
}
}
}
}

@ -16,7 +16,7 @@ namespace Volo.Abp.Identity.MongoDB
{
public class MongoOrganizationUnitRepository
: MongoDbRepository<IAbpIdentityMongoDbContext, OrganizationUnit, Guid>,
IOrganizationUnitRepository
IOrganizationUnitRepository
{
public MongoOrganizationUnitRepository(
IMongoDbContextProvider<IAbpIdentityMongoDbContext> dbContextProvider)
@ -41,8 +41,8 @@ namespace Volo.Abp.Identity.MongoDB
CancellationToken cancellationToken = default)
{
return await GetMongoQueryable()
.Where(ou => ou.Code.StartsWith(code) && ou.Id != parentId.Value)
.ToListAsync(GetCancellationToken(cancellationToken));
.Where(ou => ou.Code.StartsWith(code) && ou.Id != parentId.Value)
.ToListAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<List<OrganizationUnit>> GetListAsync(
@ -51,22 +51,28 @@ namespace Volo.Abp.Identity.MongoDB
CancellationToken cancellationToken = default)
{
return await GetMongoQueryable()
.Where(t => ids.Contains(t.Id))
.ToListAsync(GetCancellationToken(cancellationToken));
.Where(t => ids.Contains(t.Id))
.ToListAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<List<OrganizationUnit>> GetListAsync(
Guid? parentId,
string sorting = null,
int maxResultCount = int.MaxValue,
int skipCount = 0,
string filter = null,
bool includeDetails = false,
CancellationToken cancellationToken = default)
{
return await GetMongoQueryable()
.OrderBy(sorting ?? nameof(OrganizationUnit.DisplayName))
.As<IMongoQueryable<OrganizationUnit>>()
.PageBy<OrganizationUnit, IMongoQueryable<OrganizationUnit>>(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
.Where(ou=>ou.ParentId==parentId)
.WhereIf(!filter.IsNullOrWhiteSpace(),
ou => ou.DisplayName.Contains(filter) ||
ou.Code.Contains(filter))
.OrderBy(sorting ?? nameof(OrganizationUnit.DisplayName))
.As<IMongoQueryable<OrganizationUnit>>()
.PageBy<OrganizationUnit, IMongoQueryable<OrganizationUnit>>(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken));
}
public virtual async Task<OrganizationUnit> GetAsync(
@ -166,7 +172,6 @@ namespace Volo.Abp.Identity.MongoDB
return await query.CountAsync(GetCancellationToken(cancellationToken));
}
public async Task<List<IdentityUser>> GetUnaddedUsersAsync(
OrganizationUnit organizationUnit,
string sorting = null,
@ -213,7 +218,8 @@ namespace Volo.Abp.Identity.MongoDB
return Task.FromResult(0);
}
public virtual async Task RemoveAllMembersAsync(OrganizationUnit organizationUnit, CancellationToken cancellationToken = default)
public virtual async Task RemoveAllMembersAsync(OrganizationUnit organizationUnit,
CancellationToken cancellationToken = default)
{
var users = await DbContext.Users.AsQueryable()
.Where(u => u.OrganizationUnits.Any(uou => uou.OrganizationUnitId == organizationUnit.Id))
@ -227,7 +233,19 @@ namespace Volo.Abp.Identity.MongoDB
}
}
protected virtual IMongoQueryable<IdentityUser> CreateGetMembersFilteredQuery(OrganizationUnit organizationUnit, string filter = null)
public virtual async Task<long> GetLongCountAsync(Guid? parentId, string filter = null,
CancellationToken cancellationToken = default)
{
return await GetMongoQueryable()
.Where(ou=>ou.ParentId==parentId)
.WhereIf<OrganizationUnit, IMongoQueryable<OrganizationUnit>>(!filter.IsNullOrWhiteSpace(), ou =>
ou.DisplayName.Contains(filter) ||
ou.Code.Contains(filter))
.LongCountAsync(GetCancellationToken(cancellationToken));
}
protected virtual IMongoQueryable<IdentityUser> CreateGetMembersFilteredQuery(OrganizationUnit organizationUnit,
string filter = null)
{
return DbContext.Users.AsQueryable()
.Where(u => u.OrganizationUnits.Any(uou => uou.OrganizationUnitId == organizationUnit.Id))
@ -240,4 +258,4 @@ namespace Volo.Abp.Identity.MongoDB
);
}
}
}
}

@ -11,11 +11,14 @@ namespace Volo.Abp.Identity
{
private readonly IIdentityRoleAppService _roleAppService;
private readonly IIdentityRoleRepository _roleRepository;
private readonly IOrganizationUnitRepository _organizationUnitRepository;
private readonly OrganizationUnitManager _organization;
public IdentityRoleAppService_Tests()
{
_roleAppService = GetRequiredService<IIdentityRoleAppService>();
_roleRepository = GetRequiredService<IIdentityRoleRepository>();
_organization = GetRequiredService<OrganizationUnitManager>();
_organizationUnitRepository=GetRequiredService<IOrganizationUnitRepository>();
}
[Fact]
@ -81,6 +84,31 @@ namespace Volo.Abp.Identity
role.Name.ShouldBe(input.Name);
}
[Fact]
public async Task CreateWithDetailsAsync()
{
//Arrange
var input = new IdentityRoleCreateDto
{
Name = Guid.NewGuid().ToString("N").Left(8)
};
var orgInput=new OrganizationUnit(
_organization.GuidGenerator.Create(),
Guid.NewGuid().ToString("N").Left(8)
);
//Act
var result = await _roleAppService.CreateAsync(input);
await _organization.CreateAsync(orgInput);
var role = await _roleRepository.GetAsync(result.Id);
await _organization.AddRoleToOrganizationUnitAsync(role,orgInput);
//Assert
orgInput.Roles.Count.ShouldBeGreaterThan(0);
}
[Fact]
public async Task UpdateAsync()
{

@ -59,6 +59,22 @@ namespace Volo.Abp.Identity
var ous = await _organizationUnitRepository.GetListAsync(ouIds);
ous.Count.ShouldBe(2);
ous.ShouldContain(ou => ou.Id == ouIds.First());
var ou11 = await _organizationUnitRepository.GetAsync("OU11");
ou11.ShouldNotBeNull();
var ou11Children = await _organizationUnitRepository.GetListAsync(ou11.Id, includeDetails: true);
ou11Children.Count.ShouldBe(2);
}
[Fact]
public async Task GetLongCountAsync()
{
(await _organizationUnitRepository.GetLongCountAsync(_guidGenerator.Create(), filter: "11")).ShouldBe(0);
var countRoot = await _organizationUnitRepository.GetLongCountAsync(null, filter: "1");
countRoot.ShouldBe(1);
var ou11 = await _organizationUnitRepository.GetAsync("OU11");
ou11.ShouldNotBeNull();
(await _organizationUnitRepository.GetLongCountAsync(ou11.Id, "2")).ShouldBe(1);
}
[Fact]
@ -192,7 +208,7 @@ namespace Volo.Abp.Identity
{
OrganizationUnit ou1 = await _organizationUnitRepository.GetAsync("OU111", true);
OrganizationUnit ou2 = await _organizationUnitRepository.GetAsync("OU112", true);
var users = await _identityUserRepository.GetUsersInOrganizationsListAsync(new List<Guid> {ou1.Id, ou2.Id});
var users = await _identityUserRepository.GetUsersInOrganizationsListAsync(new List<Guid> { ou1.Id, ou2.Id });
users.Count.ShouldBeGreaterThan(0);
}

@ -7,6 +7,7 @@
[nzData]="nodes"
[nzTreeTemplate]="treeTemplate"
[nzExpandedKeys]="expandedKeys"
[nzExpandedIcon]="expandedIconTemplate?.template"
(nzExpandChange)="onExpandedKeysChange($event)"
(nzCheckBoxChange)="onCheckboxChange($event)"
(nzOnDrop)="onDrop($event)"
@ -18,7 +19,16 @@
[title]="node.title"
(click)="onSelectedNodeChange(node)"
>
<span>{{ node.title }}</span>
<ng-container
*ngTemplateOutlet="
customNodeTemplate?.template || defaultNodeTemplate;
context: { $implicit: node }
"
></ng-container>
<ng-template #defaultNodeTemplate>
<span>{{ node.title }}</span>
</ng-template>
<div *ngIf="menu" class="ellipsis" ngbDropdown placement="bottom" container="body">
<i class="fas fa-ellipsis-h" ngbDropdownToggle [class.dropdown-toggle]="false"></i>

@ -9,6 +9,8 @@ import {
} from '@angular/core';
import { NzFormatEmitEvent, NzFormatBeforeDropEvent } from 'ng-zorro-antd/tree';
import { of } from 'rxjs';
import { TreeNodeTemplateDirective } from '../templates/tree-node-template.directive';
import { ExpandedIconTemplateDirective } from '../templates/expanded-icon-template.directive';
export type DropEvent = NzFormatEmitEvent & { pos: number };
@ -25,6 +27,8 @@ export class TreeComponent {
dropPosition: number;
@ContentChild('menu') menu: TemplateRef<any>;
@ContentChild(TreeNodeTemplateDirective) customNodeTemplate: TreeNodeTemplateDirective;
@ContentChild(ExpandedIconTemplateDirective) expandedIconTemplate: ExpandedIconTemplateDirective;
@Output() readonly checkedKeysChange = new EventEmitter();
@Output() readonly expandedKeysChange = new EventEmitter<string[]>();
@Output() readonly selectedNodeChange = new EventEmitter();

@ -0,0 +1,8 @@
import { Directive, TemplateRef } from '@angular/core';
@Directive({
selector: '[abpTreeExpandedIconTemplate],[abp-tree-expanded-icon-template]',
})
export class ExpandedIconTemplateDirective {
constructor(public template: TemplateRef<any>) {}
}

@ -0,0 +1,8 @@
import { Directive, TemplateRef } from '@angular/core';
@Directive({
selector: '[abpTreeNodeTemplate],[abp-tree-node-template]',
})
export class TreeNodeTemplateDirective {
constructor(public template: TemplateRef<any>) {}
}

@ -1,12 +1,18 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
import { NzTreeModule } from 'ng-zorro-antd/tree';
import { TreeComponent } from './components/tree.component';
import { CommonModule } from '@angular/common';
import { TreeNodeTemplateDirective } from './templates/tree-node-template.directive';
import { ExpandedIconTemplateDirective } from './templates/expanded-icon-template.directive';
const templates = [TreeNodeTemplateDirective, ExpandedIconTemplateDirective];
const exported = [...templates, TreeComponent];
@NgModule({
imports: [CommonModule, NzTreeModule, NgbDropdownModule],
exports: [TreeComponent],
declarations: [TreeComponent],
exports: [...exported],
declarations: [...exported],
})
export class TreeModule {}

@ -1,3 +1,5 @@
export * from './lib/tree.module';
export * from './lib/components/tree.component';
export * from './lib/utils/nz-tree-adapter';
export * from './lib/templates/tree-node-template.directive';
export * from './lib/templates/expanded-icon-template.directive';

@ -3,7 +3,8 @@ import { LocaleDirection } from '@abp/ng.theme.shared';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { finalize } from 'rxjs/operators';
import { FeatureManagement } from '../../models/feature-management';
import { FeatureDto, FeaturesService, UpdateFeatureDto } from '../../proxy/feature-management';
import { FeaturesService } from '../../proxy/feature-management/features.service';
import { FeatureDto, UpdateFeatureDto } from '../../proxy/feature-management/models';
enum ValueTypes {
ToggleStringValueType = 'ToggleStringValueType',

@ -1,8 +1,7 @@
import { Injectable } from '@angular/core';
import { Store } from '@ngxs/store';
import { FeatureManagementState } from '../states';
import { FeatureManagement } from '../models';
import { GetFeatures, UpdateFeatures } from '../actions';
import { GetFeatures, UpdateFeatures } from '../actions/feature-management.actions';
import { FeatureManagementState } from '../states/feature-management.state';
@Injectable({
providedIn: 'root',

Loading…
Cancel
Save