Merge pull request #820 from Alfresco/dev-mromano-819

Flag to disable csrf
This commit is contained in:
Denys Vuika
2016-09-27 09:33:59 +01:00
committed by GitHub
14 changed files with 161 additions and 104 deletions
+5 -78
View File
@@ -3,6 +3,7 @@
The [Angular 2](https://angular.io/) based application development framework requires the following:
- An Alfresco Platform Repository (version [5.2.a-EA](https://wiki.alfresco.com/wiki/Community_file_list_201606-EA) or newer) to talk to, which has [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) enabled.
- [Download and install Activiti](https://www.alfresco.com/products/bpm/alfresco-activiti/trial)
- [Node.js](https://nodejs.org/en/) JavaScript runtime.
- [npm](https://www.npmjs.com/) package manager for JavaScript.
@@ -10,84 +11,6 @@ The [Angular 2](https://angular.io/) based application development framework req
by running `node -v` and `npm -v` in a terminal/console window.
Older versions produce errors.
## Installing Alfresco
Alfresco comes with installers that will install all the servers, webapps, and tools needed to run Alfresco.
- Download Alfresco Community from this [page](https://www.alfresco.com/products/community/download).
- Install Alfresco following these [instructions](http://docs.alfresco.com/5.1/concepts/installs-eval-intro.html).
This will install the following Alfresco web applications:
- Alfresco Platform with the Content Repository, which we need so we can access content from our custom web client
- Alfresco Solr, which we need so we can search for content from our custom web client
- Alfresco Share, not technically needed, but can be useful for creating users and groups, and to access and upload content to the repository while we are developing the custom web client
### Enable CORS in Alfresco
The web client that we are building with the application development framework will be loaded from a different web server than the Alfresco Platform is running on.
So we need to tell the Alfresco server that any request that comes in from this custom web client should be allowed access
to the Content Repository. This is done by enabling CORS.
To enable CORS in the Alfresco Platform do one of the following:
**Download and install the enable CORS module**
This is the easiest way, add the [enablecors](https://artifacts.alfresco.com/nexus/service/local/repositories/releases/content/org/alfresco/enablecors/1.0/enablecors-1.0.jar)
platform module JAR to the *$ALF_INSTALL_DIR/modules/platform* directory and restart the server.
Note. by default the CORS filter that is enabled will allow any orgin.
**Manually update the web.xml file**
Modify *$ALF_INSTALL_DIR/tomcat/webapps/alfresco/WEB-INF/web.xml* and uncomment the following section and update
`cors.allowOrigin` to `http://localhost:3000`:
```<filter>
<filter-name>CORS</filter-name>
<filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class>
<init-param>
<param-name>cors.allowGenericHttpRequests</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>cors.allowOrigin</param-name>
<param-value>http://localhost:3000</param-value>
</init-param>
<init-param>
<param-name>cors.allowSubdomains</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>cors.supportedMethods</param-name>
<param-value>GET, HEAD, POST, PUT, DELETE, OPTIONS</param-value>
</init-param>
<init-param>
<param-name>cors.supportedHeaders</param-name>
<param-value>origin, authorization, x-file-size, x-file-name, content-type, accept, x-file-type</param-value>
</init-param>
<init-param>
<param-name>cors.supportsCredentials</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>cors.maxAge</param-name>
<param-value>3600</param-value>
</init-param>
</filter>
```
When specifying the `cors.allowOrigin` URL make sure to use the URL that will be used by the web client.
Then uncomment filter mappings:
``` <filter-mapping>
<filter-name>CORS</filter-name>
<url-pattern>/api/*</url-pattern>
<url-pattern>/service/*</url-pattern>
<url-pattern>/s/*</url-pattern>
<url-pattern>/cmisbrowser/*</url-pattern>
</filter-mapping>
```
## Installing Node.js
If you don't have Node.js installed then access this [page](https://nodejs.org/en/download/) and use the appropriate installer for your OS.
@@ -99,4 +22,8 @@ $ node -v
v5.12.0
```
## Configure Nginx
To correctly configure Nginx use the following file [nginx.conf](/nginx.conf).
This will put Activiti, Alfresco and the app dev framework under the same domain.
+4
View File
@@ -3,3 +3,7 @@
display: none;
}
}
.mdl-layout-title{
font-size: 17px;
}
@@ -11,12 +11,18 @@
<span class="mdl-switch__label">BPM</span>
</label>
</p>
<p class="toggle">
<label for="switch3" class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input type="checkbox" id="switch3" class="mdl-switch__input" checked (click)="toggleCSRF()" #csrf>
<span class="mdl-switch__label">CSRF</span>
</label>
</p>
<p class="banned">
<label for="blacklistusername">Banned username</label><br>
<input id="blacklistusername" type="text" placeholder="banned username" [(ngModel)]="blackListUsername" />
</p>
</div>
<alfresco-login [providers]="providers" [fieldsValidation]="customValidation"
<alfresco-login [providers]="providers" [fieldsValidation]="customValidation" [disableCsrf]="disableCsrf"
(executeSubmit)="validateForm($event)"
(onSuccess)="onLogin($event)"
(onError)="onError($event)" #alfrescologin></alfresco-login>
@@ -33,6 +33,7 @@ export class LoginDemoComponent implements OnInit {
alfrescologin: any;
providers: string = 'ECM';
disableCsrf: boolean = false;
blackListUsername: string;
customValidation: any;
@@ -82,6 +83,10 @@ export class LoginDemoComponent implements OnInit {
}
}
toggleCSRF() {
this.disableCsrf = !this.disableCsrf;
}
validateForm(event: any) {
let values = event.values;
if (values.controls['username'].value === this.blackListUsername ) {
@@ -44,7 +44,8 @@ export class AlfrescoAuthenticationService {
ticketBpm: this.getTicketBpm(),
hostEcm: this.settingsService.ecmHost,
hostBpm: this.settingsService.bpmHost,
contextRoot: 'alfresco'
contextRoot: 'alfresco',
disableCsrf: true
});
settingsService.bpmHostSubject.subscribe((bpmHost) => {
@@ -55,6 +56,10 @@ export class AlfrescoAuthenticationService {
this.alfrescoApi.changeEcmHost(ecmHost);
});
settingsService.csrfSubject.subscribe((csrf) => {
this.alfrescoApi.changeCsrfConfig(csrf);
});
settingsService.providerSubject.subscribe((value) => {
this.alfrescoApi.config.provider = value;
});
@@ -23,11 +23,13 @@ export class AlfrescoSettingsService {
static DEFAULT_ECM_ADDRESS: string = 'http://' + window.location.hostname + ':8080';
static DEFAULT_BPM_ADDRESS: string = 'http://' + window.location.hostname + ':9999';
static DEFAULT_CSRF_CONFIG: boolean = false;
static DEFAULT_BPM_CONTEXT_PATH: string = '/activiti-app';
private _ecmHost: string = AlfrescoSettingsService.DEFAULT_ECM_ADDRESS;
private _bpmHost: string = AlfrescoSettingsService.DEFAULT_BPM_ADDRESS;
private _csrfDisabled: boolean = AlfrescoSettingsService.DEFAULT_CSRF_CONFIG;
private _bpmContextPath = AlfrescoSettingsService.DEFAULT_BPM_CONTEXT_PATH;
@@ -35,12 +37,18 @@ export class AlfrescoSettingsService {
public bpmHostSubject: Subject<string> = new Subject<string>();
public ecmHostSubject: Subject<string> = new Subject<string>();
public csrfSubject: Subject<boolean> = new Subject<boolean>();
public providerSubject: Subject<string> = new Subject<string>();
public get ecmHost(): string {
return this._ecmHost;
}
public set csrfDisabled(csrfDisabled: boolean) {
this.csrfSubject.next(csrfDisabled);
this._csrfDisabled = csrfDisabled;
}
public set ecmHost(ecmHostUrl: string) {
this.ecmHostSubject.next(ecmHostUrl);
this._ecmHost = ecmHostUrl;
@@ -17,25 +17,25 @@
import {
NodePaging,
MinimalNodeEntity,
MinimalNodeEntryEntity,
NodeMinimalEntry,
NodeMinimal,
PathInfoEntity,
ContentInfo,
NodePagingList
} from '../models/document-library.model';
export class PageNode extends NodePaging {
constructor(entries?: MinimalNodeEntity[]) {
constructor(entries?: NodeMinimalEntry[]) {
super();
this.list = new NodePagingList();
this.list.entries = entries || [];
}
}
export class FileNode extends MinimalNodeEntity {
export class FileNode extends NodeMinimalEntry {
constructor(name?: string, mimeType?: string) {
super();
this.entry = new MinimalNodeEntryEntity();
this.entry = new NodeMinimal();
this.entry.id = 'file-id';
this.entry.isFile = true;
this.entry.isFolder = false;
@@ -46,10 +46,10 @@ export class FileNode extends MinimalNodeEntity {
}
}
export class FolderNode extends MinimalNodeEntity {
export class FolderNode extends NodeMinimalEntry {
constructor(name?: string) {
super();
this.entry = new MinimalNodeEntryEntity();
this.entry = new NodeMinimal();
this.entry.id = 'folder-id';
this.entry.isFile = false;
this.entry.isFolder = true;
@@ -21,7 +21,7 @@ import { DocumentList } from './document-list';
import { DocumentListServiceMock } from './../assets/document-list.service.mock';
import { ContentActionModel } from '../models/content-action.model';
import { FileNode, FolderNode } from '../assets/document-library.model.mock';
import { MinimalNodeEntity } from '../models/document-library.model';
import { NodeMinimalEntry } from '../models/document-library.model';
describe('DocumentList', () => {
@@ -131,7 +131,7 @@ describe('DocumentList', () => {
documentButton.target = 'document';
documentList.actions = [documentButton];
let node = new MinimalNodeEntity();
let node = new NodeMinimalEntry();
expect(documentList.getNodeActions(node)).toEqual([]);
node = new FileNode();
@@ -130,7 +130,7 @@ describe('ShareDataTableAdapter', () => {
});
it('should covert cell value to formatted date', () => {
let rawValue = new Date(2015, 6, 15, 21, 43, 11).toString(); // Wed Jul 15 2015 21:43:11 GMT+0100 (BST);
let rawValue = new Date(2015, 6, 15, 21, 43, 11); // Wed Jul 15 2015 21:43:11 GMT+0100 (BST);
let dateValue = 'Jul 15, 2015, 9:43:11 PM';
let file = new FileNode();
@@ -150,7 +150,7 @@ describe('ShareDataTableAdapter', () => {
});
it('should use default date format as fallback', () => {
let rawValue = new Date(2015, 6, 15, 21, 43, 11).toString(); // Wed Jul 15 2015 21:43:11 GMT+0100 (BST);
let rawValue = new Date(2015, 6, 15, 21, 43, 11); // Wed Jul 15 2015 21:43:11 GMT+0100 (BST);
let dateValue = 'Jul 15, 2015, 9:43:11 PM';
let file = new FileNode();
@@ -170,7 +170,7 @@ describe('ShareDataTableAdapter', () => {
});
it('should return date value as string', () => {
let rawValue = new Date(2015, 6, 15, 21, 43, 11).toString(); // Wed Jul 15 2015 21:43:11 GMT+0100 (BST);
let rawValue = new Date(2015, 6, 15, 21, 43, 11); // Wed Jul 15 2015 21:43:11 GMT+0100 (BST);
let file = new FileNode();
file.entry.createdAt = rawValue;
@@ -190,7 +190,7 @@ describe('ShareDataTableAdapter', () => {
it('should log error when having date conversion issues', () => {
let dateValue = '[wrong-date]';
let file = new FileNode();
file.entry.createdAt = dateValue;
file.entry.createdAt = <any> dateValue;
let col = <DataColumn> {
key: 'createdAt',
@@ -23,7 +23,7 @@ import {
DataRow, DataColumn, DataSorting
} from 'ng2-alfresco-datatable';
import { NodePaging, MinimalNodeEntity } from './../models/document-library.model';
import { NodePaging, NodeMinimalEntry } from './../models/document-library.model';
import { DocumentListService } from './../services/document-list.service';
export class ShareDataTableAdapter implements DataTableAdapter, PaginationProvider {
@@ -305,11 +305,11 @@ export class ShareDataRow implements DataRow {
isSelected: boolean = false;
get node(): MinimalNodeEntity {
get node(): NodeMinimalEntry {
return this.obj;
}
constructor(private obj: MinimalNodeEntity) {
constructor(private obj: NodeMinimalEntry) {
if (!obj) {
throw new Error(ShareDataRow.ERR_OBJECT_NOT_FOUND);
}
@@ -17,18 +17,19 @@
// note: contains only limited subset of available fields
import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api';
export class NodePaging {
list: NodePagingList;
}
export class NodePagingList {
pagination: Pagination;
entries: MinimalNodeEntity[];
entries: NodeMinimalEntry[];
}
// TODO: rename to NodeMinimalEntry
export class MinimalNodeEntity {
entry: MinimalNodeEntryEntity;
export class NodeMinimalEntry implements MinimalNodeEntity {
entry: NodeMinimal;
}
export class Pagination {
@@ -39,17 +40,16 @@ export class Pagination {
maxItems: number;
}
// TODO: rename to NodeMinimal
export class MinimalNodeEntryEntity {
export class NodeMinimal implements MinimalNodeEntryEntity {
id: string;
parentId: string;
name: string;
nodeType: string;
isFolder: boolean;
isFile: boolean;
modifiedAt: string;
modifiedAt: Date;
modifiedByUser: UserInfo;
createdAt: string;
createdAt: Date;
createdByUser: UserInfo;
content: ContentInfo;
path: PathInfoEntity;
@@ -151,6 +151,7 @@ Attribute | Description |
Attribute | Options | Default | Description | Mandatory
--- | --- | --- | --- | ---
`providers` | *string* | ECM | Possible valid value are ECM, BPM or ALL. The default behaviour of this component will logged in only in the ECM . If you want log in in both system the correct value to use is ALL |
`disableCsrf` | *boolean* | false | To prevent the CSRF Token from been submitted. Only for Activiti call |
## Custom logo and background
@@ -51,6 +51,9 @@ export class AlfrescoLoginComponent implements OnInit {
@Input()
fieldsValidation: any;
@Input()
disableCsrf: boolean;
@Output()
onSuccess = new EventEmitter();
@@ -107,6 +110,7 @@ export class AlfrescoLoginComponent implements OnInit {
return false;
}
this.settingsService.setProviders(this.providers);
this.settingsService.csrfDisabled = this.disableCsrf;
this.disableError();
+97
View File
@@ -0,0 +1,97 @@
server {
listen 8888;
server_name dev-platform-proxy;
#set $allowOriginSite http://127.0.0.1:3000;
set $allowOriginSite *;
gzip on;
gzip_min_length 1000;
gzip_proxied expired no-cache no-store private auth;
underscores_in_headers on;
proxy_pass_request_headers on;
proxy_pass_header Set-Cookie;
location /alfresco/ {
proxy_pass http://localhost:8080/;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
proxy_redirect off;
proxy_buffering off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass_header Set-Cookie;
if ($request_method = 'OPTIONS') {
more_set_headers 'Access-Control-Allow-Origin: $allowOriginSite';
#
# Om nom nom cookies
#
more_set_headers 'Access-Control-Allow-Credentials: true';
more_set_headers 'Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS';
#
# Custom headers and headers various browsers *should* be OK with but aren't
#
more_set_headers 'Access-Control-Allow-Headers: DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type, Content-Range, Content-Disposition, Content-Description, X-CSRF-TOKEN, Authorization';
#
# Tell client that this pre-flight info is valid for 20 days
#
more_set_headers 'Access-Control-Max-Age: 1728000';
more_set_headers 'Content-Type: text/plain charset=UTF-8';
more_set_headers 'Content-Length 0';
return 204;
}
}
location /activiti/ {
proxy_pass http://localhost:9999/;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
proxy_redirect off;
proxy_buffering off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass_header Set-Cookie;
proxy_pass_request_headers on;
if ($request_method = 'OPTIONS') {
more_set_headers 'Access-Control-Allow-Origin: $allowOriginSite';
#
# Om nom nom cookies
#
more_set_headers 'Access-Control-Allow-Credentials: true';
more_set_headers 'Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS';
#
# Custom headers and headers various browsers *should* be OK with but aren't
#
more_set_headers 'Access-Control-Allow-Headers: DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type, Content-Range, Content-Disposition, Content-Description, X-CSRF-TOKEN, Authorization';
#
# Tell client that this pre-flight info is valid for 20 days
#
more_set_headers 'Access-Control-Max-Age: 1728000';
more_set_headers 'Content-Type: text/plain charset=UTF-8';
more_set_headers 'Content-Length 0';
return 204;
}
if ($request_method = 'POST') {
more_set_headers 'Access-Control-Allow-Origin: $allowOriginSite';
more_set_headers 'Access-Control-Allow-Credentials: true';
more_set_headers 'Access-Control-Allow-Methods: GET, POST, OPTIONS, DELETE';
more_set_headers 'Access-Control-Allow-Headers: DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type, Content-Range, Content-Disposition, Content-Description, X-CSRF-TOKEN, Authorization';
}
}
location / {
proxy_pass http://localhost:3000/;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
proxy_redirect off;
proxy_buffering off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-NginX-Proxy true;
proxy_pass_header Set-Cookie;
proxy_pass_request_headers on;
}
}