Merge pull request #5403 from tsiegleauq/blacklist-browsers

Catch unsupported browsers
This commit is contained in:
Emanuel Schütze 2020-06-11 15:42:45 +02:00 committed by GitHub
commit f590994875
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 204 additions and 5 deletions

View File

@ -63,6 +63,7 @@
"moment": "^2.24.0",
"ng2-charts": "^2.3.0",
"ng2-pdf-viewer": "^6.1.2",
"ngx-device-detector": "^1.4.4",
"ngx-file-drop": "^8.0.8",
"ngx-mat-select-search": "^2.1.2",
"ngx-material-timepicker": "^5.5.1",

View File

@ -7,6 +7,7 @@ import { LoginPrivacyPolicyComponent } from './site/login/components/login-priva
import { LoginWrapperComponent } from './site/login/components/login-wrapper/login-wrapper.component';
import { ResetPasswordConfirmComponent } from './site/login/components/reset-password-confirm/reset-password-confirm.component';
import { ResetPasswordComponent } from './site/login/components/reset-password/reset-password.component';
import { UnsupportedBrowserComponent } from './site/login/components/unsupported-browser/unsupported-browser.component';
/**
* Global app routing
@ -20,7 +21,8 @@ const routes: Routes = [
{ path: 'reset-password', component: ResetPasswordComponent },
{ path: 'reset-password-confirm', component: ResetPasswordConfirmComponent },
{ path: 'legalnotice', component: LoginLegalNoticeComponent },
{ path: 'privacypolicy', component: LoginPrivacyPolicyComponent }
{ path: 'privacypolicy', component: LoginPrivacyPolicyComponent },
{ path: 'unsupported-browser', component: UnsupportedBrowserComponent }
]
},
{

View File

@ -6,6 +6,7 @@ import { ActivatedRoute, Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { Subscription } from 'rxjs';
import { filter } from 'rxjs/operators';
import { AuthService } from 'app/core/core-services/auth.service';
import { OperatorService } from 'app/core/core-services/operator.service';
@ -14,6 +15,7 @@ import { OverlayService } from 'app/core/ui-services/overlay.service';
import { UserAuthType } from 'app/shared/models/users/user';
import { ParentErrorStateMatcher } from 'app/shared/parent-error-state-matcher';
import { BaseViewComponent } from 'app/site/base/base-view';
import { BrowserSupportService } from '../../services/browser-support.service';
/**
* Login mask component.
@ -31,6 +33,8 @@ export class LoginMaskComponent extends BaseViewComponent implements OnInit, OnD
*/
public hide: boolean;
private checkBrowser = true;
/**
* Reference to the SnackBarEntry for the installation notice send by the server.
*/
@ -82,7 +86,8 @@ export class LoginMaskComponent extends BaseViewComponent implements OnInit, OnD
private route: ActivatedRoute,
private formBuilder: FormBuilder,
private loginDataService: LoginDataService,
private overlayService: OverlayService
private overlayService: OverlayService,
private browserSupport: BrowserSupportService
) {
super(title, translate, matSnackBar);
// Hide the spinner if the user is at `login-mask`
@ -113,6 +118,14 @@ export class LoginMaskComponent extends BaseViewComponent implements OnInit, OnD
this.authService.redirectUser(user.id);
}
});
this.route.queryParams.pipe(filter(params => params.checkBrowser)).subscribe(params => {
this.checkBrowser = params.checkBrowser === 'true';
});
if (this.checkBrowser) {
this.checkDevice();
}
}
/**
@ -122,6 +135,12 @@ export class LoginMaskComponent extends BaseViewComponent implements OnInit, OnD
this.clearOperatorSubscription();
}
private checkDevice(): void {
if (!this.browserSupport.isBrowserSupported()) {
this.router.navigate(['./unsupported-browser'], { relativeTo: this.route });
}
}
/**
* Clears the subscription to the operator.
*/

View File

@ -1,7 +1,9 @@
<!-- The actual form -->
<header>
<mat-toolbar class="login-logo-bar" color="primary">
<a routerLink="/login"><img src="assets/img/openslides-logo-dark.svg" alt="OpenSlides-logo" /></a>
<a routerLink="/login" [queryParams]="{ checkBrowser: false }"
><img src="assets/img/openslides-logo-dark.svg" alt="OpenSlides-logo"
/></a>
</mat-toolbar>
</header>

View File

@ -0,0 +1,37 @@
<main>
<h1 class="center spacer-top-20" *ngIf="!supported">
{{ 'Your browser is not supported by OpenSlides' | translate }}
</h1>
<h1 class="center spacer-top-20" *ngIf="supported">
{{ 'Congratuations! Your browser is supported by OpenSlides' | translate }}
</h1>
<mat-card class="os-card">
<div>
<span>
{{ 'Your browser' | translate }}: {{ currentBrowser }} ({{ 'version' | translate }}:
{{ browserVersion }})
</span>
<br />
<span *ngIf="!!supportedVersion">{{ 'Minimal required version for your browser' | translate }}: {{ supportedVersion }}</span>
</div>
<div class="spacer-top-20" *ngIf="!supported">
<span>{{
'Please update your browser or contact your system administration to have your browser updated for you.'
| translate
}}</span>
</div>
<div class="spacer-top-20">
<span>
{{ 'OpenSlides recommends:' | translate }}
<ul>
<li *ngFor="let browser of recommendedBrowsers">
<a [href]="browser.url">{{ browser.name }}</a>
</li>
</ul>
</span>
</div>
</mat-card>
</main>

View File

@ -0,0 +1,27 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { E2EImportsModule } from 'e2e-imports.module';
import { UnsupportedBrowserComponent } from './unsupported-browser.component';
describe('UnsupportedBrowserComponent', () => {
let component: UnsupportedBrowserComponent;
let fixture: ComponentFixture<UnsupportedBrowserComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [E2EImportsModule],
declarations: [UnsupportedBrowserComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(UnsupportedBrowserComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,28 @@
import { Component, OnInit } from '@angular/core';
import { BrowserRecommendation, BrowserSupportService } from '../../services/browser-support.service';
@Component({
selector: 'os-unsupported-browser',
templateUrl: './unsupported-browser.component.html'
})
export class UnsupportedBrowserComponent implements OnInit {
public supported: boolean;
public currentBrowser: string;
public browserVersion: string;
public supportedVersion: number;
public get recommendedBrowsers(): BrowserRecommendation[] {
return this.browserSupprt.recommendedBrowsers;
}
public constructor(private browserSupprt: BrowserSupportService) {}
public ngOnInit(): void {
const deviceInfo = this.browserSupprt.getDeviceInfo();
this.supported = this.browserSupprt.isBrowserSupported();
this.currentBrowser = deviceInfo.browser;
this.browserVersion = deviceInfo.browser_version;
this.supportedVersion = this.browserSupprt.getSupportedVersion(deviceInfo);
}
}

View File

@ -2,6 +2,8 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { DeviceDetectorModule } from 'ngx-device-detector';
import { LoginLegalNoticeComponent } from './components/login-legal-notice/login-legal-notice.component';
import { LoginMaskComponent } from './components/login-mask/login-mask.component';
import { LoginPrivacyPolicyComponent } from './components/login-privacy-policy/login-privacy-policy.component';
@ -9,16 +11,18 @@ import { LoginWrapperComponent } from './components/login-wrapper/login-wrapper.
import { ResetPasswordConfirmComponent } from './components/reset-password-confirm/reset-password-confirm.component';
import { ResetPasswordComponent } from './components/reset-password/reset-password.component';
import { SharedModule } from '../../shared/shared.module';
import { UnsupportedBrowserComponent } from './components/unsupported-browser/unsupported-browser.component';
@NgModule({
imports: [CommonModule, RouterModule, SharedModule],
imports: [CommonModule, RouterModule, SharedModule, DeviceDetectorModule],
declarations: [
LoginWrapperComponent,
ResetPasswordComponent,
ResetPasswordConfirmComponent,
LoginMaskComponent,
LoginLegalNoticeComponent,
LoginPrivacyPolicyComponent
LoginPrivacyPolicyComponent,
UnsupportedBrowserComponent
]
})
export class LoginModule {}

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { BrowserSupportService } from './browser-support.service';
describe('BrowserSupportService', () => {
let service: BrowserSupportService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(BrowserSupportService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,63 @@
import { Injectable } from '@angular/core';
import { DeviceDetectorService, DeviceInfo } from 'ngx-device-detector';
const SmallestSupportedBrowserVersion = {
Chrome: 81,
Safari: 13,
Firefox: 68,
Opera: 66,
'MS-Edge': 81,
'MS-Edge-Chromium': 81
};
const BrowserBlacklist = ['IE'];
export interface BrowserRecommendation {
name: string;
url: string;
}
@Injectable({
providedIn: 'root'
})
export class BrowserSupportService {
public readonly recommendedBrowsers: BrowserRecommendation[] = [
{
name: 'Google Chrome',
url: 'https://www.google.com/chrome/'
},
{
name: 'Mozilla Firefox',
url: 'https://www.mozilla.org/firefox/'
}
];
public constructor(private deviceService: DeviceDetectorService) {}
public getDeviceInfo(): DeviceInfo {
return this.deviceService.getDeviceInfo();
}
public getSupportedVersion(info: DeviceInfo): number {
return SmallestSupportedBrowserVersion[info.browser];
}
/**
* Detect the browser version and forward to an error page if the browser was too old
*/
public isBrowserSupported(): boolean {
const deviceInfo = this.deviceService.getDeviceInfo();
const browser = deviceInfo.browser;
const version = parseInt(deviceInfo.browser_version, 10);
if (BrowserBlacklist.includes(browser)) {
return false;
} else if (!SmallestSupportedBrowserVersion[browser]) {
// if we don't know the browser, let's assume they are working
return true;
} else {
return version >= this.getSupportedVersion(deviceInfo);
}
}
}