parent
e6ebfd7279
commit
1f2492bb8b
@ -0,0 +1,36 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF) |
||||
# |
||||
# 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 doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require_dependency 'token/hashed_token' |
||||
|
||||
module Token |
||||
class Api < HashedToken |
||||
end |
||||
end |
@ -0,0 +1,36 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF) |
||||
# |
||||
# 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 doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require_dependency 'token/hashed_token' |
||||
|
||||
module Token |
||||
class AutoLogin < HashedToken |
||||
end |
||||
end |
@ -0,0 +1,64 @@ |
||||
# Redmine - project management software |
||||
# Copyright (C) 2006-2009 Jean-Philippe Lang |
||||
# Adapted to fit needs for mOTP |
||||
# |
||||
# 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. |
||||
|
||||
module Token |
||||
class Base < ActiveRecord::Base |
||||
self.table_name = 'tokens' |
||||
|
||||
# Hashed tokens belong to a user and are unique per type |
||||
belongs_to :user |
||||
|
||||
# Create a plain and hashed value when creating a new token |
||||
after_initialize :initialize_values |
||||
|
||||
# Ensure uniqueness of the token value |
||||
validates_presence_of :value |
||||
validates_uniqueness_of :value |
||||
|
||||
# Delete previous token of this type upon save |
||||
before_save :delete_previous_token |
||||
|
||||
## |
||||
# Find a token from the token value |
||||
def self.find_by_plaintext_value(input) |
||||
find_by(value: input) |
||||
|
||||
end |
||||
|
||||
## |
||||
# Generate a random hex token value |
||||
def self.generate_token_value |
||||
SecureRandom.hex(32) |
||||
end |
||||
|
||||
protected |
||||
|
||||
# Removes obsolete tokens (same user and action) |
||||
def delete_previous_token |
||||
if user |
||||
self.class.where(user_id: user.id, type: type).delete_all |
||||
end |
||||
end |
||||
|
||||
def initialize_values |
||||
if new_record? && !value.present? |
||||
self.value = self.class.generate_token_value |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,57 @@ |
||||
# Redmine - project management software |
||||
# Copyright (C) 2006-2009 Jean-Philippe Lang |
||||
# Adapted to fit needs for mOTP |
||||
# |
||||
# 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. |
||||
|
||||
module Token |
||||
module ExpirableToken |
||||
extend ActiveSupport::Concern |
||||
|
||||
included do |
||||
# Set the expiration time |
||||
before_create :set_expiration_time |
||||
|
||||
# Remove outdated token |
||||
after_save :delete_expired_tokens |
||||
|
||||
def valid_plaintext?(input) |
||||
return false if expired? |
||||
super |
||||
end |
||||
|
||||
def expired? |
||||
Time.now > (created_on + validity_time) |
||||
end |
||||
|
||||
def validity_time |
||||
self.class.validity_time |
||||
end |
||||
|
||||
## |
||||
# Set the expiration column |
||||
def set_expiration_time |
||||
self.expires_on = Time.now + validity_time |
||||
end |
||||
|
||||
# Delete all expired tokens |
||||
def delete_expired_tokens |
||||
if validity_time |
||||
self.class.where(["expires_on < ?", Time.now]).delete_all |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,71 @@ |
||||
# Redmine - project management software |
||||
# Copyright (C) 2006-2009 Jean-Philippe Lang |
||||
# Adapted to fit needs for mOTP |
||||
# |
||||
# 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. |
||||
|
||||
require_dependency 'token/base' |
||||
|
||||
module Token |
||||
class HashedToken < Base |
||||
# Allow access to the plain value during initial access / creation of the token |
||||
attr_reader :plain_value |
||||
|
||||
class << self |
||||
def create_and_return_value(user) |
||||
create(user: user).plain_value |
||||
end |
||||
|
||||
## |
||||
# Find a token from the token value |
||||
def find_by_plaintext_value(input) |
||||
find_by(value: hash_function(input)) |
||||
end |
||||
|
||||
private |
||||
|
||||
## |
||||
# Use a fixed salt for hashing token values. |
||||
# We still want to be able to index the hash value for fast lookups, |
||||
# so we need to determine the hash without knowing the associated user (and thus its salt) first. |
||||
def token_salt |
||||
'28f939460f6f852268a534f449928af54026af6c16aebf04f5975307b9d72de389f0'.freeze |
||||
end |
||||
end |
||||
|
||||
## |
||||
# Validate the user input on the token |
||||
# 1. The token is still valid |
||||
# 2. The plain text matches the hash |
||||
def valid_plaintext?(input) |
||||
hashed_input = hash_function(input) |
||||
ActiveSupport::SecurityUtils.secure_compare hashed_input, value |
||||
end |
||||
|
||||
def self.hash_function(input) |
||||
Digest::SHA256.hexdigest(input + token_salt) |
||||
end |
||||
delegate :hash_function, to: :class |
||||
|
||||
private |
||||
|
||||
def initialize_values |
||||
if new_record? && !value.present? |
||||
@plain_value = self.class.generate_token_value |
||||
self.value = hash_function(@plain_value) |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,41 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF) |
||||
# |
||||
# 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 doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require_dependency 'token/base' |
||||
|
||||
module Token |
||||
class Recovery < Base |
||||
include ExpirableToken |
||||
|
||||
def self.validity_time |
||||
1.day |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,45 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF) |
||||
# |
||||
# 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 doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require_dependency 'token/base' |
||||
|
||||
module Token |
||||
class Rss < Base |
||||
after_initialize do |
||||
unless value.present? |
||||
self.value = self.class.generate_token_value |
||||
end |
||||
end |
||||
|
||||
def plain_value |
||||
value |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,48 @@ |
||||
class MoveHashedTokenToCore < ActiveRecord::Migration[5.0] |
||||
class OldToken < ActiveRecord::Base |
||||
self.table_name = :plaintext_tokens |
||||
end |
||||
|
||||
def up |
||||
rename_table :tokens, :plaintext_tokens |
||||
create_tokens_table |
||||
migrate_existing_tokens |
||||
end |
||||
|
||||
def down |
||||
drop_table :tokens |
||||
rename_table :plaintext_tokens, :tokens |
||||
end |
||||
|
||||
private |
||||
|
||||
def create_tokens_table |
||||
create_table :tokens do |t| |
||||
t.references :user, index: true |
||||
t.string :type |
||||
t.string :value, default: "", null: false, limit: 128 |
||||
t.datetime :created_on, null: false |
||||
t.datetime :expires_on, null: true |
||||
end |
||||
end |
||||
|
||||
def migrate_existing_tokens |
||||
# API tokens |
||||
::Token::Api.transaction do |
||||
OldToken.where(action: 'api').find_each do |token| |
||||
result = ::Token::Api.create(user_id: token.user_id, value: ::Token::Api.hash_function(token.value)) |
||||
warn "Failed to migrate API token for ##{user.id}" unless result |
||||
end |
||||
end |
||||
|
||||
# RSS tokens |
||||
::Token::Rss.transaction do |
||||
OldToken.where(action: 'feeds').find_each do |token| |
||||
result = ::Token::Rss.create(user_id: token.user_id, value: token.value) |
||||
warn "Failed to migrate RSS token for ##{user.id}" unless result |
||||
end |
||||
end |
||||
|
||||
# We do not migrate the rest, they are short-lived anyway. |
||||
end |
||||
end |
@ -0,0 +1,67 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF) |
||||
# |
||||
# 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 doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require 'spec_helper' |
||||
|
||||
describe ::Token::HashedToken, type: :model do |
||||
let(:user) { FactoryGirl.build(:user) } |
||||
|
||||
subject { described_class.new user: user } |
||||
|
||||
describe 'token value' do |
||||
it 'is generated on a new instance' do |
||||
expect(subject.value).to be_present |
||||
end |
||||
|
||||
it 'provides the generated plain value on a new instance' do |
||||
expect(subject.valid_plaintext?(subject.plain_value)).to eq true |
||||
end |
||||
|
||||
it 'hashes the plain value to value' do |
||||
expect(subject.value).not_to eq(subject.plain_value) |
||||
end |
||||
|
||||
it 'does not keep the value when finding it' do |
||||
subject.save! |
||||
|
||||
instance = described_class.where(user: user).last |
||||
expect(instance.plain_value).to eq nil |
||||
end |
||||
end |
||||
|
||||
describe '#find_by_plaintext_value' do |
||||
before do |
||||
subject.save! |
||||
end |
||||
|
||||
it 'finds using the plaintext value' do |
||||
expect(described_class.find_by_plaintext_value(subject.plain_value)).to eq subject |
||||
expect(described_class.find_by_plaintext_value('foobar')).to eq nil |
||||
end |
||||
end |
||||
end |
Loading…
Reference in new issue