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