parent
b02c278667
commit
cd049721d5
@ -0,0 +1,70 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2020 the OpenProject GmbH |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
class Wikis::Annotate |
||||
attr_reader :lines, :content |
||||
|
||||
def initialize(content) |
||||
@content = content |
||||
current = content |
||||
current_lines = current.data.text.split(/\r?\n/) |
||||
@lines = current_lines.map { |t| [nil, nil, t] } |
||||
positions = [] |
||||
current_lines.size.times { |i| positions << i } |
||||
while current.previous |
||||
d = current.previous.data.text.split(/\r?\n/).diff(current.data.text.split(/\r?\n/)).diffs.flatten |
||||
d.each_slice(3) do |s| |
||||
sign = s[0] |
||||
line = s[1] |
||||
if sign == '+' && positions[line] && positions[line] != -1 |
||||
if @lines[positions[line]][0].nil? |
||||
@lines[positions[line]][0] = current.version |
||||
@lines[positions[line]][1] = current.data.author |
||||
end |
||||
end |
||||
end |
||||
d.each_slice(3) do |s| |
||||
sign = s[0] |
||||
line = s[1] |
||||
if sign == '-' |
||||
positions.insert(line, -1) |
||||
else |
||||
positions[line] = nil |
||||
end |
||||
end |
||||
positions.compact! |
||||
# Stop if every line is annotated |
||||
break unless @lines.detect { |line| line[0].nil? } |
||||
|
||||
current = current.previous |
||||
end |
||||
@lines.each { |line| line[0] ||= current.version } |
||||
end |
||||
end |
@ -0,0 +1,39 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2020 the OpenProject GmbH |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
class Wikis::Diff < Redmine::Helpers::Diff |
||||
attr_reader :content_to, :content_from |
||||
|
||||
def initialize(content_to, content_from) |
||||
@content_to = content_to |
||||
@content_from = content_from |
||||
super(content_to.data.text, content_from.data.text) |
||||
end |
||||
end |
@ -1,307 +0,0 @@ |
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2020 the OpenProject GmbH |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
#-- encoding: UTF-8 |
||||
module RedmineDiff |
||||
class Diff |
||||
VERSION = 0.3 |
||||
|
||||
def self.lcs(a, b) |
||||
astart = 0 |
||||
bstart = 0 |
||||
afinish = a.length - 1 |
||||
bfinish = b.length - 1 |
||||
mvector = [] |
||||
|
||||
# First we prune off any common elements at the beginning |
||||
while astart <= afinish && bstart <= afinish && a[astart] == b[bstart] |
||||
mvector[astart] = bstart |
||||
astart += 1 |
||||
bstart += 1 |
||||
end |
||||
|
||||
# now the end |
||||
while astart <= afinish && bstart <= bfinish && a[afinish] == b[bfinish] |
||||
mvector[afinish] = bfinish |
||||
afinish -= 1 |
||||
bfinish -= 1 |
||||
end |
||||
|
||||
bmatches = b.reverse_hash(bstart..bfinish) |
||||
thresh = [] |
||||
links = [] |
||||
|
||||
(astart..afinish).each do |aindex| |
||||
aelem = a[aindex] |
||||
next unless bmatches.has_key? aelem |
||||
k = nil |
||||
bmatches[aelem].reverse_each { |bindex| |
||||
if k && (thresh[k] > bindex) && (thresh[k - 1] < bindex) |
||||
thresh[k] = bindex |
||||
else |
||||
k = thresh.replacenextlarger(bindex, k) |
||||
end |
||||
links[k] = [(k == 0) ? nil : links[k - 1], aindex, bindex] if k |
||||
} |
||||
end |
||||
|
||||
if !thresh.empty? |
||||
link = links[thresh.length - 1] |
||||
while link |
||||
mvector[link[1]] = link[2] |
||||
link = link[0] |
||||
end |
||||
end |
||||
|
||||
mvector |
||||
end |
||||
|
||||
def makediff(a, b) |
||||
mvector = Diff.lcs(a, b) |
||||
ai = bi = 0 |
||||
while ai < mvector.length |
||||
bline = mvector[ai] |
||||
if bline |
||||
while bi < bline |
||||
discardb(bi, b[bi]) |
||||
bi += 1 |
||||
end |
||||
match(ai, bi) |
||||
bi += 1 |
||||
else |
||||
discarda(ai, a[ai]) |
||||
end |
||||
ai += 1 |
||||
end |
||||
while ai < a.length |
||||
discarda(ai, a[ai]) |
||||
ai += 1 |
||||
end |
||||
while bi < b.length |
||||
discardb(bi, b[bi]) |
||||
bi += 1 |
||||
end |
||||
match(ai, bi) |
||||
1 |
||||
end |
||||
|
||||
def compactdiffs |
||||
diffs = [] |
||||
@diffs.each do |df| |
||||
i = 0 |
||||
curdiff = [] |
||||
while i < df.length |
||||
whot = df[i][0] |
||||
s = @isstring ? df[i][2].chr : [df[i][2]] |
||||
p = df[i][1] |
||||
last = df[i][1] |
||||
i += 1 |
||||
while df[i] && df[i][0] == whot && df[i][1] == last + 1 |
||||
s << df[i][2] |
||||
last = df[i][1] |
||||
i += 1 |
||||
end |
||||
curdiff.push [whot, p, s] |
||||
end |
||||
diffs.push curdiff |
||||
end |
||||
diffs |
||||
end |
||||
|
||||
attr_reader :diffs, :difftype |
||||
|
||||
def initialize(diffs_or_a, b = nil, isstring = nil) |
||||
if b.nil? |
||||
@diffs = diffs_or_a |
||||
@isstring = isstring |
||||
else |
||||
@diffs = [] |
||||
@curdiffs = [] |
||||
makediff(diffs_or_a, b) |
||||
@difftype = diffs_or_a.class |
||||
end |
||||
end |
||||
|
||||
def match(_ai, _bi) |
||||
@diffs.push @curdiffs unless @curdiffs.empty? |
||||
@curdiffs = [] |
||||
end |
||||
|
||||
def discarda(i, elem) |
||||
@curdiffs.push ['-', i, elem] |
||||
end |
||||
|
||||
def discardb(i, elem) |
||||
@curdiffs.push ['+', i, elem] |
||||
end |
||||
|
||||
def compact |
||||
Diff.new(compactdiffs) |
||||
end |
||||
|
||||
def compact! |
||||
@diffs = compactdiffs |
||||
end |
||||
|
||||
def inspect |
||||
@diffs.inspect |
||||
end |
||||
end |
||||
end |
||||
|
||||
module Diffable |
||||
def diff(b) |
||||
RedmineDiff::Diff.new(self, b) |
||||
end |
||||
|
||||
# Create a hash that maps elements of the array to arrays of indices |
||||
# where the elements are found. |
||||
|
||||
def reverse_hash(range = (0...length)) |
||||
revmap = {} |
||||
range.each do |i| |
||||
elem = self[i] |
||||
if revmap.has_key? elem |
||||
revmap[elem].push i |
||||
else |
||||
revmap[elem] = [i] |
||||
end |
||||
end |
||||
revmap |
||||
end |
||||
|
||||
def replacenextlarger(value, high = nil) |
||||
high ||= length |
||||
if self.empty? || value > self[-1] |
||||
push value |
||||
return high |
||||
end |
||||
# binary search for replacement point |
||||
low = 0 |
||||
while low < high |
||||
index = (high + low) / 2 |
||||
found = self[index] |
||||
return nil if value == found |
||||
if value > found |
||||
low = index + 1 |
||||
else |
||||
high = index |
||||
end |
||||
end |
||||
|
||||
self[low] = value |
||||
# $stderr << "replace #{value} : 0/#{low}/#{init_high} (#{steps} steps) (#{init_high-low} off )\n" |
||||
# $stderr.puts self.inspect |
||||
# gets |
||||
# p length - low |
||||
low |
||||
end |
||||
|
||||
def patch(diff) |
||||
newary = nil |
||||
if diff.difftype == String |
||||
newary = diff.difftype.new('') |
||||
else |
||||
newary = diff.difftype.new |
||||
end |
||||
ai = 0 |
||||
bi = 0 |
||||
diff.diffs.each do |d| |
||||
d.each { |mod| |
||||
case mod[0] |
||||
when '-' |
||||
while ai < mod[1] |
||||
newary << self[ai] |
||||
ai += 1 |
||||
bi += 1 |
||||
end |
||||
ai += 1 |
||||
when '+' |
||||
while bi < mod[1] |
||||
newary << self[ai] |
||||
ai += 1 |
||||
bi += 1 |
||||
end |
||||
newary << mod[2] |
||||
bi += 1 |
||||
else |
||||
raise 'Unknown diff action' |
||||
end |
||||
} |
||||
end |
||||
while ai < length |
||||
newary << self[ai] |
||||
ai += 1 |
||||
bi += 1 |
||||
end |
||||
newary |
||||
end |
||||
end |
||||
|
||||
class Array |
||||
include Diffable |
||||
end |
||||
|
||||
class String |
||||
include Diffable |
||||
end |
||||
|
||||
# = Diff |
||||
# (({diff.rb})) - computes the differences between two arrays or |
||||
# strings. Copyright (C) 2001 Lars Christensen |
||||
# |
||||
# == Synopsis |
||||
# |
||||
# diff = Diff.new(a, b) |
||||
# b = a.patch(diff) |
||||
# |
||||
# == Class Diff |
||||
# === Class Methods |
||||
# --- Diff.new(a, b) |
||||
# --- a.diff(b) |
||||
# Creates a Diff object which represent the differences between |
||||
# ((|a|)) and ((|b|)). ((|a|)) and ((|b|)) can be either be arrays |
||||
# of any objects, strings, or object of any class that include |
||||
# module ((|Diffable|)) |
||||
# |
||||
# == Module Diffable |
||||
# The module ((|Diffable|)) is intended to be included in any class for |
||||
# which differences are to be computed. Diffable is included into String |
||||
# and Array when (({diff.rb})) is (({require}))'d. |
||||
# |
||||
# Classes including Diffable should implement (({[]})) to get element at |
||||
# integer indices, (({<<})) to append elements to the object and |
||||
# (({ClassName#new})) should accept 0 arguments to create a new empty |
||||
# object. |
||||
# |
||||
# === Instance Methods |
||||
# --- Diffable#patch(diff) |
||||
# Applies the differences from ((|diff|)) to the object ((|obj|)) |
||||
# and return the result. ((|obj|)) is not changed. ((|obj|)) and |
||||
# can be either an array or a string, but must match the object |
||||
# from which the ((|diff|)) was created. |
@ -0,0 +1,274 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2020 the OpenProject GmbH |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
module Bcf::Viewpoints |
||||
class CreateContract < ::ModelContract |
||||
include Bcf::ManageBcfGuarded |
||||
|
||||
WHITELISTED_PROPERTIES = %w(guid |
||||
index |
||||
snapshot |
||||
orthogonal_camera |
||||
perspective_camera |
||||
clipping_planes |
||||
bitmaps |
||||
lines |
||||
components).freeze |
||||
|
||||
ORTHOGONAL_CAMERA_PROPERTIES = %w(camera_view_point |
||||
camera_direction |
||||
camera_up_vector |
||||
view_to_world_scale).freeze |
||||
|
||||
PERSPECTIVE_CAMERA_PROPERTIES = %w(camera_view_point |
||||
camera_direction |
||||
camera_up_vector |
||||
field_of_view).freeze |
||||
|
||||
LINES_PROPERTIES = %w(start_point |
||||
end_point).freeze |
||||
|
||||
CLIPPING_PLANES_PROPERTIES = %w(location |
||||
direction).freeze |
||||
|
||||
COMPONENTS_PROPERTIES = %w(visibility |
||||
selection |
||||
coloring).freeze |
||||
|
||||
COMPONENT_PROPERTIES = %w(ifc_guid |
||||
originating_system |
||||
authoring_tool_id).freeze |
||||
|
||||
COLORING_PROPERTIES = %w(color |
||||
components).freeze |
||||
|
||||
VISIBILITY_PROPERTIES = %w(default_visibility |
||||
exceptions |
||||
view_setup_hints).freeze |
||||
|
||||
VIEW_SETUP_HINTS_PROPERTIES = %w(spaces_visible |
||||
space_boundaries_visible |
||||
openings_visible).freeze |
||||
|
||||
COLOR_REGEXP = /#([0-9a-f]{2})?[0-9a-f]{6}/ |
||||
|
||||
WHITELISTED_DIMENSIONS = %w(x y z).freeze |
||||
|
||||
attribute :uuid |
||||
attribute :issue |
||||
attribute :json_viewpoint do |
||||
validate_json_viewpoint_present |
||||
validate_json_viewpoint_hash |
||||
|
||||
next if errors.any? |
||||
|
||||
validate_properties |
||||
validate_snapshot |
||||
validate_index |
||||
validate_orthogonal_camera |
||||
validate_perspective_camera |
||||
validate_lines |
||||
validate_clipping_planes |
||||
validate_bitmaps |
||||
validate_components |
||||
validate_guid |
||||
end |
||||
|
||||
def validate_json_viewpoint_present |
||||
errors.add(:json_viewpoint, :blank) unless viewpoint.present? |
||||
end |
||||
|
||||
def validate_json_viewpoint_hash |
||||
errors.add(:json_viewpoint, :no_json) if viewpoint.present? && !viewpoint.is_a?(Hash) |
||||
end |
||||
|
||||
def validate_properties |
||||
errors.add(:json_viewpoint, :unsupported_key) if viewpoint.present? && (viewpoint.keys - WHITELISTED_PROPERTIES).any? |
||||
end |
||||
|
||||
def validate_snapshot |
||||
return unless (sjson = viewpoint['snapshot']) |
||||
|
||||
errors.add(:json_viewpoint, :snapshot_type_unsupported) unless %w(jpg png).include? sjson['snapshot_type'] |
||||
errors.add(:json_viewpoint, :snapshot_data_blank) unless sjson['snapshot_data'].present? |
||||
end |
||||
|
||||
def validate_index |
||||
return unless (ijson = viewpoint['index']) |
||||
|
||||
errors.add(:json_viewpoint, :index_not_integer) unless ijson.is_a? Integer |
||||
end |
||||
|
||||
def validate_orthogonal_camera |
||||
return unless (ocjson = viewpoint['orthogonal_camera']) |
||||
|
||||
if ocjson.keys != ORTHOGONAL_CAMERA_PROPERTIES || |
||||
ocjson.except('view_to_world_scale').any? { |_, direction| invalid_direction?(direction) } || |
||||
!ocjson['view_to_world_scale'].is_a?(Numeric) |
||||
errors.add(:json_viewpoint, :invalid_orthogonal_camera) |
||||
end |
||||
end |
||||
|
||||
def validate_perspective_camera |
||||
return unless (pcjson = viewpoint['perspective_camera']) |
||||
|
||||
if pcjson.keys != PERSPECTIVE_CAMERA_PROPERTIES || |
||||
pcjson.except('field_of_view').any? { |_, direction| invalid_direction?(direction) } || |
||||
!pcjson['field_of_view'].is_a?(Numeric) |
||||
errors.add(:json_viewpoint, :invalid_perspective_camera) |
||||
end |
||||
end |
||||
|
||||
def validate_lines |
||||
return unless (ljson = viewpoint['lines']) |
||||
|
||||
if !ljson.is_a?(Array) || |
||||
ljson.any? { |line| invalid_line?(line) } |
||||
errors.add(:json_viewpoint, :invalid_lines) |
||||
end |
||||
end |
||||
|
||||
def validate_clipping_planes |
||||
return unless (cpjson = viewpoint['clipping_planes']) |
||||
|
||||
if !cpjson.is_a?(Array) || |
||||
cpjson.any? { |cp| invalid_clipping_plane?(cp) } |
||||
errors.add(:json_viewpoint, :invalid_clipping_planes) |
||||
end |
||||
end |
||||
|
||||
def validate_bitmaps |
||||
errors.add(:json_viewpoint, :bitmaps_not_writable) if viewpoint['bitmaps'] |
||||
end |
||||
|
||||
def validate_components |
||||
return unless (cjson = viewpoint['components']) |
||||
|
||||
if !cjson.is_a?(Hash) || |
||||
invalid_components_properties?(cjson) |
||||
errors.add(:json_viewpoint, :invalid_components) |
||||
end |
||||
end |
||||
|
||||
def validate_guid |
||||
return unless (json_guid = viewpoint['guid']) |
||||
|
||||
errors.add(:json_viewpoint, :mismatching_guid) if json_guid != model.uuid |
||||
end |
||||
|
||||
def invalid_components_properties?(json) |
||||
(json.keys - COMPONENTS_PROPERTIES).any? || |
||||
invalid_visibility?(json['visibility']) || |
||||
invalid_components?(json['selection']) || |
||||
invalid_colorings?(json['coloring']) |
||||
end |
||||
|
||||
def invalid_line?(line) |
||||
invalid_hash_point?(line, LINES_PROPERTIES) |
||||
end |
||||
|
||||
def invalid_clipping_plane?(line) |
||||
invalid_hash_point?(line, CLIPPING_PLANES_PROPERTIES) |
||||
end |
||||
|
||||
def invalid_hash_point?(hash, whitelist) |
||||
!hash.is_a?(Hash) || |
||||
hash.keys != whitelist || |
||||
hash.values.any? { |v| invalid_point?(v) } |
||||
end |
||||
|
||||
def invalid_visibility?(visibility) |
||||
visibility.nil? || |
||||
!visibility.is_a?(Hash) || |
||||
(visibility.keys - VISIBILITY_PROPERTIES).any? || |
||||
invalid_default_visibility?(visibility['default_visibility']) || |
||||
invalid_components?(visibility['exceptions']) || |
||||
invalid_view_setup_hints?(visibility['view_setup_hints']) |
||||
end |
||||
|
||||
def invalid_components?(components) |
||||
return false unless components.present? |
||||
|
||||
!components.is_a?(Array) || components.any? { |component| invalid_component?(component) } |
||||
end |
||||
|
||||
def invalid_colorings?(colorings) |
||||
return false unless colorings.present? |
||||
|
||||
!colorings.is_a?(Array) || colorings.any? { |coloring| invalid_coloring?(coloring) } |
||||
end |
||||
|
||||
def invalid_component?(component) |
||||
!component.is_a?(Hash) || |
||||
component.empty? || |
||||
(component.keys - COMPONENT_PROPERTIES).any? || |
||||
component.values.any? { |v| !v.is_a?(String) } |
||||
end |
||||
|
||||
def invalid_coloring?(coloring) |
||||
!coloring.is_a?(Hash) || |
||||
coloring.keys != COLORING_PROPERTIES || |
||||
invalid_color?(coloring['color']) || |
||||
invalid_components?(coloring['components']) |
||||
end |
||||
|
||||
def invalid_color?(color) |
||||
!(color.is_a?(String) && color.match?(COLOR_REGEXP)) |
||||
end |
||||
|
||||
def invalid_direction?(direction) |
||||
!direction.is_a?(Hash) || |
||||
direction.keys != WHITELISTED_DIMENSIONS || |
||||
direction.values.any? { |v| !v.is_a? Numeric } |
||||
end |
||||
alias_method :invalid_point?, :invalid_direction? |
||||
|
||||
def invalid_default_visibility?(visibility) |
||||
visibility.present? && |
||||
no_boolean?(visibility) |
||||
end |
||||
|
||||
def invalid_view_setup_hints?(hints) |
||||
return false if hints.nil? |
||||
|
||||
!hints.is_a?(Hash) || |
||||
(hints.keys - VIEW_SETUP_HINTS_PROPERTIES).any? || |
||||
hints.values.any? { |v| no_boolean?(v) } |
||||
end |
||||
|
||||
def no_boolean?(property) |
||||
!(property.is_a?(TrueClass) || property.is_a?(FalseClass)) |
||||
end |
||||
|
||||
def viewpoint |
||||
model.json_viewpoint |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,51 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is an open source project management software. |
||||
# Copyright (C) 2012-2020 the OpenProject GmbH |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
module Bcf |
||||
module ManageBcfGuarded |
||||
extend ActiveSupport::Concern |
||||
|
||||
included do |
||||
def validate |
||||
validate_user_allowed_to_manage |
||||
|
||||
super |
||||
end |
||||
|
||||
private |
||||
|
||||
def validate_user_allowed_to_manage |
||||
unless model.project && user.allowed_to?(:manage_bcf, model.project) |
||||
errors.add :base, :error_unauthorized |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,26 @@ |
||||
module ::TwoFactorAuthentication |
||||
module BackupCodes |
||||
extend ActiveSupport::Concern |
||||
|
||||
## |
||||
# Request user to enter backup code |
||||
def enter_backup_code |
||||
render |
||||
end |
||||
|
||||
## |
||||
# Verify backup code |
||||
def verify_backup_code |
||||
code = params[:backup_code] |
||||
return fail_login(t('two_factor_authentication.error_invalid_backup_code')) unless code.present? |
||||
|
||||
service = TwoFactorAuthentication::UseBackupCodeService.new user: @authenticated_user |
||||
result = service.verify code |
||||
if result.success? |
||||
complete_stage_redirect |
||||
else |
||||
fail_login(t('two_factor_authentication.error_invalid_backup_code')) |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,98 @@ |
||||
module ::TwoFactorAuthentication |
||||
module RememberToken |
||||
extend ActiveSupport::Concern |
||||
|
||||
included do |
||||
helper_method :has_valid_2fa_remember_token? |
||||
helper_method :remember_2fa_enabled? |
||||
helper_method :remember_2fa_days |
||||
end |
||||
|
||||
## |
||||
# Check for valid 2FA autologin cookie and log in the user |
||||
# if that's the case |
||||
def perform_2fa_authentication_with_remember(service) |
||||
if has_valid_2fa_remember_token?(@authenticated_user) |
||||
complete_stage_redirect |
||||
else |
||||
perform_2fa_authentication service |
||||
end |
||||
end |
||||
|
||||
## |
||||
# Set a 2FA autologin cookie for the user (if supported). |
||||
def set_remember_token! |
||||
return unless remember_2fa_enabled? |
||||
return unless params[:remember_me].present? |
||||
|
||||
cookies.encrypted[remember_cookie_name] = { |
||||
value: new_token!(@authenticated_user), |
||||
httponly: true, |
||||
expires: remember_2fa_days.days.from_now, |
||||
secure: Setting.protocol == 'https' |
||||
} |
||||
end |
||||
|
||||
## |
||||
# Remove the 2FA autologin cookie |
||||
# and all potentially stored tokens |
||||
def clear_remember_token!(user = current_user) |
||||
cookies.delete remember_cookie_name |
||||
|
||||
::TwoFactorAuthentication::RememberedAuthToken |
||||
.where(user: user) |
||||
.delete_all |
||||
end |
||||
|
||||
def remember_2fa_enabled? |
||||
remember_2fa_days > 0 |
||||
end |
||||
|
||||
## |
||||
# Return whether for the given user, |
||||
# any valid remember tokens exist (not necessary in this session!) |
||||
def any_remember_token_present?(user = current_user) |
||||
return false unless remember_2fa_enabled? |
||||
|
||||
::TwoFactorAuthentication::RememberedAuthToken |
||||
.not_expired |
||||
.where(user: user) |
||||
.exists? |
||||
end |
||||
|
||||
## |
||||
# Return whether the user has a valid remember token |
||||
# that is identified by his cookie value. |
||||
def has_valid_2fa_remember_token?(user = current_user) |
||||
return false unless remember_2fa_enabled? |
||||
|
||||
token = get_2fa_remember_token(user) |
||||
token.present? && !token.expired? |
||||
end |
||||
|
||||
## |
||||
# Try to read a Remember token from the cookie |
||||
def get_2fa_remember_token(user) |
||||
value = cookies.encrypted[remember_cookie_name] |
||||
return false unless value.present? |
||||
|
||||
::TwoFactorAuthentication::RememberedAuthToken |
||||
.where(user: user) |
||||
.find_by_plaintext_value value |
||||
end |
||||
|
||||
def remember_2fa_days |
||||
OpenProject::TwoFactorAuthentication::TokenStrategyManager.allow_remember_for_days |
||||
end |
||||
|
||||
private |
||||
|
||||
def remember_cookie_name |
||||
:op2fa_remember_token |
||||
end |
||||
|
||||
def new_token!(user) |
||||
::TwoFactorAuthentication::RememberedAuthToken.create_and_return_value(user) |
||||
end |
||||
end |
||||
end |
@ -1,28 +0,0 @@ |
||||
module ::TwoFactorAuthentication |
||||
module Concerns |
||||
module BackupCodes |
||||
extend ActiveSupport::Concern |
||||
|
||||
## |
||||
# Request user to enter backup code |
||||
def enter_backup_code |
||||
render |
||||
end |
||||
|
||||
## |
||||
# Verify backup code |
||||
def verify_backup_code |
||||
code = params[:backup_code] |
||||
return fail_login(t('two_factor_authentication.error_invalid_backup_code')) unless code.present? |
||||
|
||||
service = TwoFactorAuthentication::UseBackupCodeService.new user: @authenticated_user |
||||
result = service.verify code |
||||
if result.success? |
||||
complete_stage_redirect |
||||
else |
||||
fail_login(t('two_factor_authentication.error_invalid_backup_code')) |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
@ -1,100 +0,0 @@ |
||||
module ::TwoFactorAuthentication |
||||
module Concerns |
||||
module RememberToken |
||||
extend ActiveSupport::Concern |
||||
|
||||
included do |
||||
helper_method :has_valid_2fa_remember_token? |
||||
helper_method :remember_2fa_enabled? |
||||
helper_method :remember_2fa_days |
||||
end |
||||
|
||||
## |
||||
# Check for valid 2FA autologin cookie and log in the user |
||||
# if that's the case |
||||
def perform_2fa_authentication_with_remember(service) |
||||
if has_valid_2fa_remember_token?(@authenticated_user) |
||||
complete_stage_redirect |
||||
else |
||||
perform_2fa_authentication service |
||||
end |
||||
end |
||||
|
||||
## |
||||
# Set a 2FA autologin cookie for the user (if supported). |
||||
def set_remember_token! |
||||
return unless remember_2fa_enabled? |
||||
return unless params[:remember_me].present? |
||||
|
||||
cookies.encrypted[remember_cookie_name] = { |
||||
value: new_token!(@authenticated_user), |
||||
httponly: true, |
||||
expires: remember_2fa_days.days.from_now, |
||||
secure: Setting.protocol == 'https' |
||||
} |
||||
end |
||||
|
||||
## |
||||
# Remove the 2FA autologin cookie |
||||
# and all potentially stored tokens |
||||
def clear_remember_token!(user = current_user) |
||||
cookies.delete remember_cookie_name |
||||
|
||||
::TwoFactorAuthentication::RememberedAuthToken |
||||
.where(user: user) |
||||
.delete_all |
||||
end |
||||
|
||||
def remember_2fa_enabled? |
||||
remember_2fa_days > 0 |
||||
end |
||||
|
||||
## |
||||
# Return whether for the given user, |
||||
# any valid remember tokens exist (not necessary in this session!) |
||||
def any_remember_token_present?(user = current_user) |
||||
return false unless remember_2fa_enabled? |
||||
|
||||
::TwoFactorAuthentication::RememberedAuthToken |
||||
.not_expired |
||||
.where(user: user) |
||||
.exists? |
||||
end |
||||
|
||||
## |
||||
# Return whether the user has a valid remember token |
||||
# that is identified by his cookie value. |
||||
def has_valid_2fa_remember_token?(user = current_user) |
||||
return false unless remember_2fa_enabled? |
||||
|
||||
token = get_2fa_remember_token(user) |
||||
token.present? && !token.expired? |
||||
end |
||||
|
||||
## |
||||
# Try to read a Remember token from the cookie |
||||
def get_2fa_remember_token(user) |
||||
value = cookies.encrypted[remember_cookie_name] |
||||
return false unless value.present? |
||||
|
||||
::TwoFactorAuthentication::RememberedAuthToken |
||||
.where(user: user) |
||||
.find_by_plaintext_value value |
||||
end |
||||
|
||||
def remember_2fa_days |
||||
OpenProject::TwoFactorAuthentication::TokenStrategyManager.allow_remember_for_days |
||||
end |
||||
|
||||
private |
||||
|
||||
def remember_cookie_name |
||||
:op2fa_remember_token |
||||
end |
||||
|
||||
def new_token!(user) |
||||
::TwoFactorAuthentication::RememberedAuthToken.create_and_return_value(user) |
||||
end |
||||
end |
||||
end |
||||
end |
Loading…
Reference in new issue