Skip to content

Commit

Permalink
Merge branch 'main' into BC-5971-copying-internal-links
Browse files Browse the repository at this point in the history
  • Loading branch information
Metauriel authored Dec 18, 2023
2 parents b543d69 + 32720d3 commit aedf0ee
Show file tree
Hide file tree
Showing 119 changed files with 3,248 additions and 144 deletions.
14 changes: 14 additions & 0 deletions ansible/roles/schulcloud-server-core/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@
template: onepassword.yml.j2
when: ONEPASSWORD_OPERATOR is defined and ONEPASSWORD_OPERATOR|bool

- name: Admin API server ConfigMap
kubernetes.core.k8s:
kubeconfig: ~/.kube/config
namespace: "{{ NAMESPACE }}"
template: admin-api-server-configmap.yml.j2
apply: yes

- name: Admin API server Secret (from 1Password)
kubernetes.core.k8s:
kubeconfig: ~/.kube/config
namespace: "{{ NAMESPACE }}"
template: admin-api-server-onepassword.yml.j2
when: ONEPASSWORD_OPERATOR is defined and ONEPASSWORD_OPERATOR|bool

- name: Admin API client secret (from 1Password)
kubernetes.core.k8s:
kubeconfig: ~/.kube/config
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: admin-api-server-configmap
namespace: {{ NAMESPACE }}
labels:
app: api-admin
data:
NODE_OPTIONS: "--max-old-space-size=3072"
NEST_LOG_LEVEL: "info"
ADMIN_API__PORT: "4030"
SC_DOMAIN: "{{ DOMAIN }}"
FEATURE_PROMETHEUS_METRICS_ENABLED: "true"
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ spec:
protocol: TCP
envFrom:
- configMapRef:
name: api-configmap
name: admin-api-server-configmap
- secretRef:
name: api-secret
name: admin-api-server-secret
command: ['npm', 'run', 'nest:start:admin-api-server:prod']
resources:
limits:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apiVersion: onepassword.com/v1
kind: OnePasswordItem
metadata:
name: admin-api-server-secret
namespace: {{ NAMESPACE }}
labels:
app: api-admin
spec:
itemPath: "vaults/{{ ONEPASSWORD_OPERATOR_VAULT }}/items/admin-api-server"
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ metadata:
spec:
type: ClusterIP
ports:
# port for http managing drawing data
# Admin API server port.
- port: 4030
targetPort: 4030
protocol: TCP
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,14 @@ data:
SANIS_CLIENT_SECRET=$(node scripts/secret.js -s $AES_KEY -e $SANIS_CLIENT_SECRET)
SANIS_SYSTEM_ID=0000d186816abba584714c93
if [[ $SC_THEME == "n21" ]]; then
mongosh $DATABASE__URL --quiet --eval 'db.schools.updateOne(
mongosh $DATABASE__URL --quiet --eval 'db.schools.updateMany(
{
"_id": ObjectId("5f2987e020834114b8efd6f8")
"_id": {
$in: [
ObjectId("5f2987e020834114b8efd6f8"),
ObjectId("5fa2c5ccb229544f2c69666c")
]
}
},
{
$set: { "systems" : [ ObjectId("'$SANIS_SYSTEM_ID'") ] }
Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/infra/mail/interfaces/mail-config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export interface MailConfig {
ADDITIONAL_BLACKLISTED_EMAIL_DOMAINS: string[];
BLOCKLIST_OF_EMAIL_DOMAINS: string[];
}
2 changes: 1 addition & 1 deletion apps/server/src/infra/mail/mail.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class MailService {
@Inject('MAIL_SERVICE_OPTIONS') private readonly options: MailServiceOptions,
private readonly configService: ConfigService<MailConfig, true>
) {
this.domainBlacklist = this.configService.get<string[]>('ADDITIONAL_BLACKLISTED_EMAIL_DOMAINS');
this.domainBlacklist = this.configService.get<string[]>('BLOCKLIST_OF_EMAIL_DOMAINS');
}

public async send(data: Mail): Promise<void> {
Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/modules/account/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from './account.module';
export * from './account-config';
export { AccountService, AccountDto } from './services';
export { AccountService, AccountDto, AccountSaveDto } from './services';
2 changes: 1 addition & 1 deletion apps/server/src/modules/account/services/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './account.service';
export { AccountDto } from './dto';
export { AccountDto, AccountSaveDto } from './dto';
2 changes: 2 additions & 0 deletions apps/server/src/modules/authorization/authorization.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
LegacySchoolRule,
LessonRule,
SchoolExternalToolRule,
SchoolSystemOptionsRule,
SubmissionRule,
SystemRule,
TaskRule,
Expand Down Expand Up @@ -45,6 +46,7 @@ import { FeathersAuthorizationService, FeathersAuthProvider } from './feathers';
UserLoginMigrationRule,
LegacySchoolRule,
SystemRule,
SchoolSystemOptionsRule,
],
exports: [FeathersAuthorizationService, AuthorizationService, SystemRule],
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ export * from './user-login-migration.rule';
export * from './user.rule';
export * from './group.rule';
export { SystemRule } from './system.rule';
export { SchoolSystemOptionsRule } from './school-system-options.rule';
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { ObjectId } from '@mikro-orm/mongodb';
import { SchoolSystemOptions } from '@modules/legacy-school';
import { Test, TestingModule } from '@nestjs/testing';
import { SchoolEntity, SystemEntity, User } from '@shared/domain/entity';
import { Permission } from '@shared/domain/interface';
import {
schoolFactory,
schoolSystemOptionsFactory,
setupEntities,
systemEntityFactory,
userFactory,
} from '@shared/testing';
import { AuthorizationContextBuilder } from '../mapper';
import { AuthorizationHelper } from '../service/authorization.helper';
import { SchoolSystemOptionsRule } from './school-system-options.rule';

describe(SchoolSystemOptionsRule.name, () => {
let module: TestingModule;
let rule: SchoolSystemOptionsRule;

let authorizationHelper: DeepMocked<AuthorizationHelper>;

beforeAll(async () => {
await setupEntities();

module = await Test.createTestingModule({
providers: [
SchoolSystemOptionsRule,
{
provide: AuthorizationHelper,
useValue: createMock<AuthorizationHelper>(),
},
],
}).compile();

rule = module.get(SchoolSystemOptionsRule);
authorizationHelper = module.get(AuthorizationHelper);
});

afterAll(async () => {
await module.close();
});

afterEach(() => {
jest.resetAllMocks();
});

describe('isApplicable', () => {
describe('when the entity is applicable', () => {
const setup = () => {
const user: User = userFactory.buildWithId();
const schoolSystemOptions: SchoolSystemOptions = schoolSystemOptionsFactory.build();

return {
user,
schoolSystemOptions,
};
};

it('should return true', () => {
const { user, schoolSystemOptions } = setup();

const result = rule.isApplicable(user, schoolSystemOptions);

expect(result).toEqual(true);
});
});

describe('when the entity is not applicable', () => {
const setup = () => {
const user: User = userFactory.buildWithId();

return {
user,
};
};

it('should return false', () => {
const { user } = setup();

const result = rule.isApplicable(user, {} as unknown as SchoolSystemOptions);

expect(result).toEqual(false);
});
});
});

describe('hasPermission', () => {
describe('when the user accesses a system at his school with the required permissions', () => {
const setup = () => {
const systemEntity: SystemEntity = systemEntityFactory.buildWithId();
const school: SchoolEntity = schoolFactory.buildWithId({
systems: [systemEntity],
});
const schoolSystemOptions: SchoolSystemOptions = schoolSystemOptionsFactory.build({
systemId: systemEntity.id,
schoolId: school.id,
});
const user: User = userFactory.buildWithId({ school });
const authorizationContext = AuthorizationContextBuilder.read([Permission.SCHOOL_SYSTEM_VIEW]);

authorizationHelper.hasAllPermissions.mockReturnValueOnce(true);

return {
user,
schoolSystemOptions,
authorizationContext,
};
};

it('should check the permission', () => {
const { user, schoolSystemOptions, authorizationContext } = setup();

rule.hasPermission(user, schoolSystemOptions, authorizationContext);

expect(authorizationHelper.hasAllPermissions).toHaveBeenCalledWith(
user,
authorizationContext.requiredPermissions
);
});

it('should return true', () => {
const { user, schoolSystemOptions, authorizationContext } = setup();

const result = rule.hasPermission(user, schoolSystemOptions, authorizationContext);

expect(result).toEqual(true);
});
});

describe('when the user accesses a system at his school, but does not have the required permissions', () => {
const setup = () => {
const systemEntity: SystemEntity = systemEntityFactory.buildWithId();
const school: SchoolEntity = schoolFactory.buildWithId({
systems: [systemEntity],
});
const schoolSystemOptions: SchoolSystemOptions = schoolSystemOptionsFactory.build({
systemId: systemEntity.id,
schoolId: school.id,
});
const user: User = userFactory.buildWithId({ school });
const authorizationContext = AuthorizationContextBuilder.read([Permission.SCHOOL_SYSTEM_VIEW]);

authorizationHelper.hasAllPermissions.mockReturnValueOnce(false);

return {
user,
schoolSystemOptions,
authorizationContext,
};
};

it('should return false', () => {
const { user, schoolSystemOptions, authorizationContext } = setup();

const result = rule.hasPermission(user, schoolSystemOptions, authorizationContext);

expect(result).toEqual(false);
});
});

describe('when the system is not part of the users school', () => {
const setup = () => {
const systemEntity: SystemEntity = systemEntityFactory.buildWithId();
const school: SchoolEntity = schoolFactory.buildWithId({
systems: [systemEntity],
});
const schoolSystemOptions: SchoolSystemOptions = schoolSystemOptionsFactory.build({
systemId: new ObjectId().toHexString(),
schoolId: school.id,
});
const user: User = userFactory.buildWithId({ school });
const authorizationContext = AuthorizationContextBuilder.read([Permission.SCHOOL_SYSTEM_VIEW]);

authorizationHelper.hasAllPermissions.mockReturnValueOnce(true);

return {
user,
schoolSystemOptions,
authorizationContext,
};
};

it('should return false', () => {
const { user, schoolSystemOptions, authorizationContext } = setup();

const result = rule.hasPermission(user, schoolSystemOptions, authorizationContext);

expect(result).toEqual(false);
});
});

describe('when the user is not at the school', () => {
const setup = () => {
const schoolSystemOptions: SchoolSystemOptions = schoolSystemOptionsFactory.build();
const systemEntity: SystemEntity = systemEntityFactory.buildWithId();
const school: SchoolEntity = schoolFactory.buildWithId({
systems: [systemEntity],
});
const user: User = userFactory.buildWithId({ school });
const authorizationContext = AuthorizationContextBuilder.read([Permission.SCHOOL_SYSTEM_VIEW]);

authorizationHelper.hasAllPermissions.mockReturnValueOnce(true);

return {
user,
schoolSystemOptions,
authorizationContext,
};
};

it('should return false', () => {
const { user, schoolSystemOptions, authorizationContext } = setup();

const result = rule.hasPermission(user, schoolSystemOptions, authorizationContext);

expect(result).toEqual(false);
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { AnyProvisioningOptions, SchoolSystemOptions } from '@modules/legacy-school';
import { Injectable } from '@nestjs/common';
import { User } from '@shared/domain/entity';
import { AuthorizationHelper } from '../service/authorization.helper';
import { AuthorizationContext, Rule } from '../type';

@Injectable()
export class SchoolSystemOptionsRule implements Rule<SchoolSystemOptions> {
constructor(private readonly authorizationHelper: AuthorizationHelper) {}

public isApplicable(user: User, domainObject: SchoolSystemOptions): boolean {
const isMatched: boolean = domainObject instanceof SchoolSystemOptions<AnyProvisioningOptions>;

return isMatched;
}

public hasPermission(user: User, domainObject: SchoolSystemOptions, context: AuthorizationContext): boolean {
const hasPermissions: boolean = this.authorizationHelper.hasAllPermissions(user, context.requiredPermissions);

const isAtSchool: boolean = user.school.id === domainObject.schoolId;

const hasSystem: boolean = user.school.systems.getIdentifiers().includes(domainObject.systemId);

const isAuthorized: boolean = hasPermissions && isAtSchool && hasSystem;

return isAuthorized;
}
}
Loading

0 comments on commit aedf0ee

Please sign in to comment.