Merge remote-tracking branch 'origin/dev' into release/9.0

pull/7437/head
Oliver Günther 6 years ago
commit e2d3dda392
  1. 2
      README.md
  2. 5
      app/assets/stylesheets/content/work_packages/timelines/elements/_bar.sass
  3. 1
      app/assets/stylesheets/content/work_packages/timelines/elements/_labels.sass
  4. 8
      app/services/add_attachment_service.rb
  5. 6
      db/migrate/20180510184732_rename_planning_elemnt_type_colors_to_colors.rb
  6. 4
      docker/mysql-to-postgres/bin/migrate-mysql-to-postgres
  7. 111
      docs/operations/migrating/docker/postgresql-migration.md
  8. 188
      docs/operations/migrating/manual/postgresql-migration.md
  9. 47
      docs/security/README.md
  10. 102
      docs/security/security-at-openproject.com.asc
  11. 5
      frontend/src/app/components/wp-card-view/wp-card-view.component.html
  12. 7
      frontend/src/app/components/wp-card-view/wp-card-view.component.sass
  13. 32
      frontend/src/app/components/wp-table/timeline/cells/timeline-cell-renderer.ts
  14. 6
      frontend/src/app/components/wp-table/timeline/cells/timeline-milestone-cell-renderer.ts
  15. 1
      frontend/src/app/components/wp-table/timeline/wp-timeline.ts

@ -38,6 +38,8 @@ will keep our community secure. If you happen to come across a security issue we
you to disclose it to us privately to allow our users and community enough time to
upgrade. Security issues will always take precedence over anything else in the pipeline.
For more information on how to disclosure a security vulnerability, [please see this page](docs/security/README.md).
## License
OpenProject is licensed under the terms of the GNU General Public License version 3.

@ -5,8 +5,9 @@
float: left
z-index: 0
// Fallback color
background: #555
.timeline-element--bg
width: 100%
height: 100%
&:hover:not(.-clamp-style)
.leftHandle, .rightHandle

@ -22,6 +22,7 @@
// Position container left of bar
position: absolute
left: 0px
top: 0px
// Then translate by its own width + some margin
transform: translateX(calc(-100% - 10px))
font-size: 12px

@ -55,7 +55,11 @@ class AddAttachmentService
ActiveRecord::Base.transaction do
attachment.save!
if container.respond_to? :add_journal
add_journal if container.respond_to? :add_journal
end
end
def add_journal
# reload to get the newly added attachment
container.attachments.reload
container.add_journal author
@ -66,5 +70,3 @@ class AddAttachmentService
container.save!(validate: false)
end
end
end
end

@ -6,9 +6,13 @@ class RenamePlanningElemntTypeColorsToColors < ActiveRecord::Migration[5.1]
rename_index :planning_element_type_colors, :timelines_colors_pkey, :planning_element_type_colors_pkey
end
if ActiveRecord::Base.connection.execute("SELECT 1 as value FROM pg_class c WHERE c.relkind = 'S' and c.relname = 'planning_element_type_colors_id_seq'").to_a.present?
if ActiveRecord::Base.connection.execute("SELECT 1 as value FROM pg_class c WHERE c.relkind = 'S' and c.relname = 'planning_element_type_colors_id_seq'").to_a.present? ||
begin
puts "Renaming id_seq to pkey which seems to be required by rename_table"
rename_index :planning_element_type_colors, :planning_element_type_colors_id_seq, :planning_element_type_colors_pkey
rescue => e
raise e unless e.message.match? /planning_element_type_colors_pkey.+?already exists/
end
end
rename_table :planning_element_type_colors, :colors

@ -18,7 +18,7 @@ if db_url.scheme.start_with?("mysql")
exit 0 # MySQL still supported until 9.x - with 10.0 we must make this an error (exit 1)
end
if db_url.scheme.start_with?("postgresql") && mysql_url.nil?
if db_url.scheme.start_with?("postgres") && mysql_url.nil?
# nothing to do
exit 0
end
@ -108,7 +108,7 @@ puts "Importing database ..."
mysql_url.query = nil
mysql_url.scheme = "mysql"
_, pgloader_status = run "pgloader-ccl --verbose #{mysql_url} #{db_url}", record_output: false
_, pgloader_status = run "pgloader-ccl --with \"preserve index names\" --verbose #{mysql_url} #{db_url}", record_output: false
if pgloader_status != 0
puts "\nFailed to import MySQL database into Postgres. See above."

@ -0,0 +1,111 @@
# Migrating your Docker OpenProject database to PostgreSQL
This guide will migrate your docker-based MySQL installation to a PostgreSQL installation using [pgloader](https://github.com/dimitri/pgloader).
## Backing up
Before beginning the migration, please ensure you have created a backup of your current installation. Please follow our [backup and restore documentation](https://www.openproject.org/operations/backup/backup-guide-docker-installation/) for Docker-based installations.
## Built-in migration script
The Dockerfile comes with a built-in PostgreSQL migration script that will auto-run and inform you what to do.
### Set up a PostgreSQL database
Depending on your usage, you may want to set up an external PostgreSQL database to provide the container with connection details just like you did for MySQL.
In any case, you may also use the internally configured PostgreSQL instance of the docker container by using the DATABASE_URL ` postgres://openproject:openproject@127.0.0.1/openproject`
**Installing a PostgreSQL database outside docker**
If you want to set up a PostgreSQL installation database outside the container and not use the built-in database, please set up a PostgreSQL database now. These are generic apt-based installation steps, please adapt them appropriately for your distribution.
OpenProject requires at least PostgreSQL 9.5 installed. Please check <https://www.postgresql.org/download/> if your distributed package is too old.
```bash
[root@host] apt-get install postgresql postgresql-contrib libpq-dev
```
Once installed, switch to the PostgreSQL system user.
```bash
[root@host] su - postgres
```
Then, as the PostgreSQL user, create the system user for OpenProject. This will prompt you for a password. We are going to assume in the following guide that password were 'openproject'. Of course, please choose a strong password and replace the values in the following guide with it!
```bash
[postgres@host] createuser -W openproject
```
Next, create the database owned by the new user
```bash
[postgres@host] createdb -O openproject openproject
```
Lastly, exit the system user
```bash
[postgres@host] exit
# You will be root again now.
```
### Setting environment variables
To run the migration part of the image, you will have to provide two environment files:
### The MYSQL_DATABASE_URL
Note down or copy the current MySQL `DATABASE_URL`
```bash
# Will look something something of the kind
# mysql2://user:password@localhost:3306/dbname
# Pass into the container but replace mysql2 with mysql!
MYSQL_DATABSAE_URL="mysql://user:password@localhost:3306/dbname"
```
**Please note:** Ensure that the URL starts with `mysql://` , not with ` mysql2://` !
### The PostgreSQL DATABASE_URL
Pass in `DATABASE_URL` pointing to your new PostgreSQL database. This is either the default `postgres://openproject:openproject@127.0.0.1/openproject` or if you set up a PostgreSQL installation above, use credentials for your installation you set up above.
```bash
POSTGRES_DATABASE_URL="postgresql://<USER>:<PASSWORD>@<HOST>/<Database name>"
```
### Running the migration
To run the migration script within the container, now simply run the following command, replacing the content of the environment variables with your actual values.
```bash
docker run \
-it openproject/community:latest
-e MYSQL_DATABASE_URL="mysql://user:password@localhost:3306/dbname" \
-e DATABASE_URL="postgresql://openproject:<PASSWORD>@localhost:5432/openproject"
```
This will perform all necessary steps to perform the migration. Afterwards, simply remove the `MYSQL_DATABASE_URL`environment variable again and start your container as usual.

@ -0,0 +1,188 @@
# Migrating your manual-installation OpenProject database to PostgreSQL
This guide will migrate your MySQL installation on a manual installation to a PostgreSQL installation using [pgloader](https://github.com/dimitri/pgloader).
## Backing up
Before beginning the migration, please ensure you have created a backup of your current installation. Please follow our [backup and restore documentation](https://www.openproject.org/operations/backup/backup-guide-manual-installation/) for Docker-based installations.
## Set up a PostgreSQL database
Please first set up a PostgreSQL database. These are generic apt-based installation steps, please adapt them appropriately for your distribution.
OpenProject requires at least PostgreSQL 9.5 installed. Please check <https://www.postgresql.org/download/> if your distributed package is too old.
```bash
[root@host] apt-get install postgresql postgresql-contrib libpq-dev
```
Once installed, switch to the PostgreSQL system user.
```bash
[root@host] su - postgres
```
Then, as the PostgreSQL user, create the system user for OpenProject. This will prompt you for a password. We are going to assume in the following guide that password were 'openproject'. Of course, please choose a strong password and replace the values in the following guide with it!
```bash
[postgres@host] createuser -W openproject
```
Next, create the database owned by the new user
```bash
[postgres@host] createdb -O openproject openproject
```
Lastly, exit the system user
```bash
[postgres@host] exit
# You will be root again now.
```
## The MYSQL_DATABASE_URL
Note down or copy the current MySQL `DATABASE_URL`. The following command exports it to the curent shell as `MYSQL_DATABASE_URL`:
```bash
# Will look something something of the kind
# mysql2://user:password@localhost:3306/dbname
# Pass into the container but replace mysql2 with mysql!
export MYSQL_DATABSAE_URL="mysql://user:password@localhost:3306/dbname"
```
**Please note:** Ensure that the URL starts with `mysql://` , not with ` mysql2://` !
## The PostgreSQL DATABASE_URL
Pass in `DATABASE_URL` pointing to your new PostgreSQL database. Fill the template below with the password you entered above.
```bash
export POSTGRES_DATABASE_URL="postgresql://openproject:<PASSWORD>@localhost/openproject
```
## Running the migration via Docker
OpenProject provides a simple conversion script that you can run as a single command via Docker.
To run the migration script within the container, simply run the following command, replacing the content of the environment variables with your actual values.
```bash
docker run \
-it openproject/community:latest
-e MYSQL_DATABASE_URL=$MYSQL_DATABASE_URL \
-e DATABASE_URL=$POSTGRES_DATABASE_URL
```
This will perform all necessary steps to perform the migration. Afterwards, simply remove the `MYSQL_DATABASE_URL`environment variable again and start your container as usual.
## Running the migration without Docker
### Installation of pgloader
#### Apt Systems
For systems with APT package managers (Debian, Ubuntu), you should already have `pgloader` available and can install as root with with:
```
[root@host] apt-get install pgloader
```
[For other installations, please see the project page itself for steps on installing with Docker or from source](https://github.com/dimitri/pgloader#install).
After installation, check that pgloader is in your path and accessible:
```
[root@host] pgloader --version
# Should output something of the kind
pgloader version "3.5.2"
compiled with SBCL 1.4.5.debian
```
### Performing the migration
You are now ready to use `pgloader`. You simply point it the old and new database URL while specifying the option
`--with "preserve index names"` which ensures that index names are kept identical.
```bash
pgloader --verbose --with "preserve index names" $MYSQL_DATABASE_URL $POSTGRES_DATABASE_URL
```
This might take a while depending on current installation size.
### Index attachments for fulltext search
One of the benefits of using PostgreSQL over MySql is the support for fulltext search on attachments. The fulltext search feature relies on the existence of two additional columns for attachments that need to be added now ff the migration to PostgreSql is done for an OpenProject >= **8.0**. If the OpenProject version is below **8.0** the next two commands can be skipped.
In order to add the necessary columns to the database, run
```bash
openproject run rails db:migrate:redo VERSION=20180122135443
```
After the columns have been added, the index has to be created for already uploaded attachments
```bash
openproject run rails attachments:extract_fulltext_where_missing
```
If a large set of attachments already exists, executing the command might take a while.
### Indexes on relations table
You will also need to rebuild the index on the relations table. Simply run the following command
to re-run the migration.
```bash
openproject run rails db:migrate:redo VERSION=20180105130053
```
## Optional: Uninstall MySQL
If you installed MySQL only for the installation of OpenProject, evaluate whether you want to remove MySQL server.
You can check the output of `dpkg - l | grep mysql` to check for packages removable. Only keep `libmysqlclient-dev` for Ruby dependencies on the mysql adapter.
The following is an exemplary removal of an installed version MySQL 5.7.
```
[root@host] apt-get remove mysql-server
```

@ -0,0 +1,47 @@
## Statement on Security
At its core, OpenProject is an open-source software that is [developed and published on GitHub](https://github.com/opf/openproject). Every change to the OpenProject code base ends up in an open repository accessible to everyone. This results in a transparent software where every commit can be traced back to the contributor.
Automated tests and manual code reviews ensure that these contributions are safe for the entire community of OpenProject. These tests encompass the correctness of security and access control features. We have ongoing collaborations with security professionals from to test the OpenProject code base for security exploits.
### Security announcements mailing list
We provide a mailing list for security advisories on OpenProject at <https://groups.google.com/forum/#!forum/openproject-security>. Please register there to get immediate notifications as we publish them.
Any security related information will also be published on our blog and website at https://www.openproject.com
### Reporting a vulnerability
We take all facets of security seriously at OpenProject. If you want to report a security concerns, have remarks, or contributions regarding security at OpenProject, please reach out to us at [security@openproject.com](mailto:security@openproject.com).
If you can, please send us a PGP-encrypted email using the following key:
- Key ID: [0x7D669C6D47533958](https://pgp.mit.edu/pks/lookup?op=get&search=0x7D669C6D47533958) ,
- Fingerprint BDCF E01E DE84 EA19 9AE1 72CE 7D66 9C6D 4753 3958
- You may also find the key [attached in our OpenProject repository.](https://github.com/opf/openproject/blob/dev/docs/security/security-at-openproject.com.asc)
Please include a description on how to reproduce the issue if possible. Our security team will get your email and will attempt to reproduce and fix the issue as soon as possible.
## OpenProject Security features
### Authentication.
OpenProject administrators can enforce [authentication mechanisms and password rules]() to ensure users choose secure passwords according to current industry standards. Passwords stored by OpenProject are securely stored using salted bcrypt. Alternatively, external authentication providers and protocols (such as LDAP, SAML) can be enforced to avoid using and exposing passwords within OpenProject.
### Two-step user registration
In compliance with common requirements in works committees, ensure that new users added by project responsibles are confirmed by a superior before allowing the user to enter the system for the first time.
### User management and access control.
Administrators are provided with [fine-grained role-based access control mechanisms]() to ensure that users are only seeing and accessing the data they are allowed to on an individual project level.
### Two-Factor authentication. (Cloud or Enterprise Edition)
Secure your authentication mechanisms with a second factor by TOTP standard (or SMS, depending on your instance) to be entered by users upon logging in. [More information]().

@ -0,0 +1,102 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Comment: User-ID: OpenProject Security <security@openproject.com>
Comment: Fingerprint: BDCFE01EDE84EA199AE172CE7D669C6D47533958
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
8K1kfQ4x8CiE2edqerbpajtKTiix
=WLl/
-----END PGP PUBLIC KEY BLOCK-----

@ -49,6 +49,11 @@
<span [textContent]="wpTypeAttribute(wp)"
class="wp-card--type"
[ngClass]="typeHighlightingClass(wp)"></span>
<a uiSref="work-packages.show"
[uiParams]="{workPackageId: wp.id}"
class="wp-card--id">
#{{wp.id}}
</a>
<span [textContent]="wpSubject(wp)"
class="wp-card--subject"></span>
<wp-status-button *ngIf="showStatusButton"

@ -19,7 +19,7 @@
width: 100%
border: 1px solid var(--widget-box-block-border-color)
border-radius: 2px
padding: 10px 20px 10px 10px
padding: 10px 12px 10px 10px
margin-top: 10px
position: relative
box-shadow: 1px 1px 3px 0px lightgrey
@ -38,7 +38,7 @@
grid-auto-columns: auto 1fr auto
grid-auto-rows: auto auto auto
grid-row-gap: 10px
grid-template-areas: "type type type" "subject subject subject" "attributeTag avatar date"
grid-template-areas: "type type type" "subject subject subject" "attributeTag avatar idlink"
overflow: hidden
.wp-card--type
@ -51,6 +51,9 @@
.wp-card--assignee
grid-area: avatar
place-self: center left
.wp-card--id
text-align: right
grid-area: idlink
.wp-card--status
grid-area: attributeTag
max-width: 120px

@ -3,7 +3,7 @@ import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-r
import {
calculatePositionValueForDayCount,
calculatePositionValueForDayCountingPx,
RenderInfo,
RenderInfo, timelineBackgroundElementClass,
timelineElementCssClass,
timelineMarkerSelectionStartClass
} from '../wp-timeline';
@ -204,17 +204,18 @@ export class TimelineCellRenderer {
* @return true, if the element should still be displayed.
* false, if the element must be removed from the timeline.
*/
public update(bar:HTMLDivElement, labels:WorkPackageCellLabels|null, renderInfo:RenderInfo):boolean {
public update(element:HTMLDivElement, labels:WorkPackageCellLabels|null, renderInfo:RenderInfo):boolean {
const changeset = renderInfo.changeset;
const bar = element.querySelector(`.${timelineBackgroundElementClass}`) as HTMLElement;
const viewParams = renderInfo.viewParams;
let start = moment(changeset.value('startDate'));
let due = moment(changeset.value('dueDate'));
if (_.isNaN(start.valueOf()) && _.isNaN(due.valueOf())) {
bar.style.visibility = 'hidden';
element.style.visibility = 'hidden';
} else {
bar.style.visibility = 'visible';
element.style.visibility = 'visible';
}
// only start date, fade out bar to the right
@ -232,16 +233,16 @@ export class TimelineCellRenderer {
// offset left
const offsetStart = start.diff(viewParams.dateDisplayStart, 'days');
bar.style.left = calculatePositionValueForDayCount(viewParams, offsetStart);
element.style.left = calculatePositionValueForDayCount(viewParams, offsetStart);
// duration
const duration = due.diff(start, 'days') + 1;
bar.style.width = calculatePositionValueForDayCount(viewParams, duration);
element.style.width = calculatePositionValueForDayCount(viewParams, duration);
// ensure minimum width
if (!_.isNaN(start.valueOf()) || !_.isNaN(due.valueOf())) {
const minWidth = _.max([renderInfo.viewParams.pixelPerDay, 2]);
bar.style.minWidth = minWidth + 'px';
element.style.minWidth = minWidth + 'px';
}
// Update labels if any
@ -306,17 +307,20 @@ export class TimelineCellRenderer {
* start to finish date.
*/
public render(renderInfo:RenderInfo):HTMLDivElement {
const container = document.createElement('div');
const bar = document.createElement('div');
const left = document.createElement('div');
const right = document.createElement('div');
bar.className = timelineElementCssClass + ' ' + this.type;
container.className = timelineElementCssClass + ' ' + this.type;
bar.className = timelineBackgroundElementClass;
left.className = classNameLeftHandle;
right.className = classNameRightHandle;
bar.appendChild(left);
bar.appendChild(right);
container.appendChild(bar);
container.appendChild(left);
container.appendChild(right);
return bar;
return container;
}
createAndAddLabels(renderInfo:RenderInfo, element:HTMLElement):WorkPackageCellLabels {
@ -362,15 +366,15 @@ export class TimelineCellRenderer {
return labels;
}
protected applyTypeColor(wp:WorkPackageResource, element:HTMLElement):void {
protected applyTypeColor(wp:WorkPackageResource, bg:HTMLElement):void {
let type = wp.type;
if (!type) {
element.style.backgroundColor = this.fallbackColor;
bg.style.backgroundColor = this.fallbackColor;
}
const id = type.id;
element.classList.add(Highlighting.backgroundClass('type', id!));
bg.classList.add(Highlighting.backgroundClass('type', id!));
}
protected assignDate(changeset:WorkPackageChangeset, attributeName:string, value:moment.Moment) {

@ -2,7 +2,7 @@ import * as moment from 'moment';
import {WorkPackageResource} from 'core-app/modules/hal/resources/work-package-resource';
import {
calculatePositionValueForDayCountingPx,
RenderInfo,
RenderInfo, timelineBackgroundElementClass,
timelineElementCssClass
} from '../wp-timeline';
import {CellDateMovement, LabelPosition, TimelineCellRenderer} from './timeline-cell-renderer';
@ -125,8 +125,8 @@ export class TimelineMilestoneCellRenderer extends TimelineCellRenderer {
const diamond = jQuery('.diamond', element)[0];
element.style.width = 15 + 'px';
element.style.height = 15 + 'px';
diamond.style.width = 15 + 'px';
diamond.style.height = 15 + 'px';
diamond.style.width = 15 + 'px';
diamond.style.height = 15 + 'px';
diamond.style.marginLeft = -(15 / 2) + (renderInfo.viewParams.pixelPerDay / 2) + 'px';

@ -34,6 +34,7 @@ import {RenderedRow} from '../../wp-fast-table/builders/primary-render-pass';
import Moment = moment.Moment;
export const timelineElementCssClass = 'timeline-element';
export const timelineBackgroundElementClass = 'timeline-element--bg';
export const timelineGridElementCssClass = 'wp-timeline--grid-element';
export const timelineMarkerSelectionStartClass = 'selection-start';

Loading…
Cancel
Save