landable 1.13.2 → 1.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/.rubocop.yml +5 -0
  2. data/CHANGELOG.md +7 -1
  3. data/Gemfile +3 -0
  4. data/app/controllers/landable/api/access_tokens_controller.rb +19 -1
  5. data/app/controllers/landable/api/pages_controller.rb +1 -1
  6. data/app/controllers/landable/api/templates_controller.rb +3 -3
  7. data/app/models/landable/access_token.rb +25 -2
  8. data/app/models/landable/author.rb +16 -2
  9. data/app/models/landable/page.rb +1 -1
  10. data/app/models/landable/template.rb +4 -2
  11. data/app/models/landable/template_revision.rb +1 -0
  12. data/app/responders/landable/page_render_responder.rb +1 -3
  13. data/app/serializers/landable/access_token_serializer.rb +1 -1
  14. data/app/serializers/landable/author_serializer.rb +1 -1
  15. data/app/serializers/landable/template_serializer.rb +5 -0
  16. data/app/services/landable/authentication_service.rb +1 -1
  17. data/db/migrate/20130510221424_create_landable_schema.rb +10 -10
  18. data/db/migrate/20150610999999_add_permissions_to_access_tokens.rb +6 -0
  19. data/db/migrate/20150728195345_add_category_column_to_templates.rb +13 -0
  20. data/doc/schema/access_token.json +4 -0
  21. data/doc/schema/author.json +12 -0
  22. data/doc/schema/permissions.json +21 -0
  23. data/doc/schema/template.json +4 -0
  24. data/doc/schema/template_revision.json +4 -0
  25. data/features/api/access_tokens.feature +2 -2
  26. data/features/step_definitions/core_api_steps.rb +3 -3
  27. data/features/step_definitions/factory_steps.rb +4 -0
  28. data/features/support/env.rb +1 -1
  29. data/landable.gemspec +3 -2
  30. data/lib/landable.rb +4 -2
  31. data/lib/landable/configuration.rb +58 -1
  32. data/lib/landable/traffic/tracker.rb +17 -17
  33. data/lib/landable/version.rb +2 -2
  34. data/spec/controllers/landable/api/configuration_controller_spec.rb +6 -0
  35. data/spec/controllers/landable/api_controller_spec.rb +1 -1
  36. data/spec/dummy/bin/rails +1 -1
  37. data/spec/dummy/config.ru +1 -1
  38. data/spec/dummy/config/environments/test.rb +1 -1
  39. data/spec/dummy/config/initializers/landable.rb +1 -1
  40. data/spec/dummy/etc/audit_flags.yml +1 -0
  41. data/spec/dummy/etc/ldap.yml +17 -0
  42. data/spec/dummy/etc/sandiego.yml +85 -0
  43. data/spec/dummy/etc/sitemap_extra.yml +12 -0
  44. data/spec/factories/authors.rb +18 -2
  45. data/spec/models/landable/access_token_spec.rb +2 -1
  46. data/spec/models/landable/page_spec.rb +1 -1
  47. data/spec/services/landable/authentication_service_spec.rb +1 -1
  48. data/spec/services/landable/render_service_spec.rb +1 -1
  49. metadata +31 -4
@@ -1,3 +1,8 @@
1
+ AllCops:
2
+ Exclude:
3
+ - 'lib/landable/layout.rb'
4
+ - 'lib/landable/partial.rb'
5
+
1
6
  Lint/AmbiguousOperator:
2
7
  Enabled: true
3
8
 
@@ -2,7 +2,13 @@
2
2
 
3
3
  See README.md before updating this file.
4
4
 
5
- ## Unreleased [#](https://github.com/enova/landable/compare/v1.13.2...master)
5
+ ## Unreleased [#](https://github.com/enova/landable/compare/v1.14.0...master)
6
+
7
+ ## 1.14.0 [#](https://github.com/enova/landable/compare/v1.13.2...v1.14.0)
8
+ * Feature: Support permissions for an author
9
+ * Feature: Support Figgy configuration options
10
+ * Add Figgy and Responders gems
11
+ * Database updates
6
12
 
7
13
  ## 1.13.2 [#](https://github.com/enova/landable/compare/v1.13.1...v1.13.2)
8
14
  * Bugfix: Fix landable traffic errors
data/Gemfile CHANGED
@@ -7,6 +7,9 @@ gemspec
7
7
  # concerned only with compatibility (see bin/test)
8
8
  gem 'rails', ENV['RAILS_VERSION'] if ENV.key? 'RAILS_VERSION'
9
9
 
10
+ # handle configurations!
11
+ gem 'figgy', '~> 1.1.0'
12
+
10
13
  # development/test dependencies, and anything else that doesn't belong or fit
11
14
  # in the gemspec
12
15
  group :test do
@@ -11,9 +11,12 @@ module Landable
11
11
 
12
12
  def create
13
13
  ident = AuthenticationService.call(asset_token_params[:username], asset_token_params[:password])
14
+
14
15
  author = RegistrationService.call(ident)
15
16
 
16
- respond_with AccessToken.create!(author: author), status: :created
17
+ permissions = determine_permissions(ident[:groups])
18
+
19
+ respond_with AccessToken.create!(author: author, permissions: permissions), status: :created
17
20
  rescue Landable::AuthenticationFailedError
18
21
  head :unauthorized
19
22
  end
@@ -42,6 +45,21 @@ module Landable
42
45
  def asset_token_params
43
46
  params.require(:access_token).permit(:username, :password)
44
47
  end
48
+
49
+ def determine_permissions(user_groups)
50
+ yaml_groups = Landable.configuration['ldap'][:permissions]
51
+ permissions_groups = user_groups.select { |group| yaml_groups.include?(group) }
52
+
53
+ user_permissions = {}
54
+ permissions_groups.each do |group|
55
+ group_permissions = yaml_groups[group].keys
56
+ group_permissions.each do |perm|
57
+ user_permissions[perm] ||= yaml_groups[group][perm]
58
+ end
59
+ end
60
+
61
+ user_permissions
62
+ end
45
63
  end
46
64
  end
47
65
  end
@@ -53,7 +53,7 @@ module Landable
53
53
  end
54
54
 
55
55
  def preview
56
- page = Page.where(page_id: page_params[:id]).first_or_initialize
56
+ page = Page.where(page_id: page_params[:id]).first_or_initialize
57
57
  page.attributes = page_params
58
58
 
59
59
  # run the validators and render
@@ -51,9 +51,9 @@ module Landable
51
51
  # custom methods
52
52
  def preview
53
53
  template = Template.new(template_params)
54
- theme = Theme.most_used_on_pages
54
+ theme = Theme.most_used_on_pages
55
55
 
56
- page = Page.example(theme: theme, body: template.body)
56
+ page = Page.example(theme: theme, body: template.body)
57
57
 
58
58
  content = generate_preview_for(page)
59
59
 
@@ -72,7 +72,7 @@ module Landable
72
72
 
73
73
  def template_params
74
74
  params.require(:template).permit(:id, :name, :body, :description, :thumbnail_url,
75
- :slug, :is_layout, :is_publishable,
75
+ :slug, :is_layout, :is_publishable, :category_id,
76
76
  audit_flags: [])
77
77
  end
78
78
 
@@ -2,19 +2,42 @@ module Landable
2
2
  class AccessToken < ActiveRecord::Base
3
3
  include Landable::TableName
4
4
 
5
+ # Maximum token age, in hours
6
+ MAX_AGE = ((Landable.configuration['ldap'] &&
7
+ Landable.configuration['ldap'][:access_token_max_age]) || 8).hours
8
+
5
9
  belongs_to :author
6
10
  validates_presence_of :author_id
7
11
  validates_presence_of :expires_at
12
+ validates_presence_of :permissions
8
13
 
9
14
  before_validation do |token|
10
- token.expires_at ||= 8.hours.from_now
15
+ token.expires_at ||= expiration
11
16
  end
12
17
 
13
18
  scope :fresh, -> { where('expires_at > ?', Time.zone.now) }
14
19
  scope :expired, -> { where('expires_at <= ?', Time.zone.now) }
15
20
 
16
21
  def refresh!
17
- update_column :expires_at, 8.hours.from_now
22
+ update_column :expires_at, expiration
23
+ end
24
+
25
+ def can_publish?
26
+ permissions['publish'] == 'true'
27
+ end
28
+
29
+ def can_edit?
30
+ permissions['edit'] == 'true'
31
+ end
32
+
33
+ def can_read?
34
+ permissions['read'] == 'true'
35
+ end
36
+
37
+ private
38
+
39
+ def expiration
40
+ MAX_AGE.from_now
18
41
  end
19
42
  end
20
43
  end
@@ -5,9 +5,23 @@ module Landable
5
5
 
6
6
  def self.authenticate!(username, token_id)
7
7
  author = where(username: username).first
8
- return unless author
9
- return unless author.access_tokens.fresh.exists?(token_id)
8
+ return unless author && author.access_tokens.fresh.exists?(token_id)
10
9
  author
11
10
  end
11
+
12
+ def can_read
13
+ token = access_tokens.fresh.last
14
+ token.present? && token.can_read?
15
+ end
16
+
17
+ def can_edit
18
+ token = access_tokens.fresh.last
19
+ token.present? && token.can_edit?
20
+ end
21
+
22
+ def can_publish
23
+ token = access_tokens.fresh.last
24
+ token.present? && token.can_publish?
25
+ end
12
26
  end
13
27
  end
@@ -30,7 +30,7 @@ module Landable
30
30
  validates :redirect_url, url: true, allow_blank: true
31
31
  validate :hero_asset_existence
32
32
 
33
- belongs_to :theme, class_name: 'Landable::Theme', inverse_of: :pages, counter_cache: true
33
+ belongs_to :theme, class_name: 'Landable::Theme', inverse_of: :pages, counter_cache: true
34
34
  belongs_to :published_revision, class_name: 'Landable::PageRevision'
35
35
  belongs_to :category, class_name: 'Landable::Category'
36
36
  belongs_to :updated_by_author, class_name: 'Landable::Author'
@@ -13,14 +13,15 @@ module Landable
13
13
  before_save :slug_has_no_spaces
14
14
 
15
15
  belongs_to :published_revision, class_name: 'Landable::TemplateRevision'
16
+ belongs_to :category, class_name: 'Landable::Category'
16
17
  has_many :audits, class_name: 'Landable::Audit', as: :auditable
17
18
  has_many :revisions, class_name: 'Landable::TemplateRevision'
18
19
 
19
- has_and_belongs_to_many :pages, join_table: Page.templates_join_table_name
20
+ has_and_belongs_to_many :pages, join_table: Page.templates_join_table_name
20
21
 
21
22
  delegate :count, to: :pages, prefix: true # Returns how many Pages a Template lives in!
22
23
 
23
- before_save lambda { |template|
24
+ before_save lambda { |template|
24
25
  template.is_publishable = true unless template.published_revision_id_changed?
25
26
  }
26
27
 
@@ -61,6 +62,7 @@ module Landable
61
62
  self.name = revision.name
62
63
  self.body = revision.body
63
64
  self.description = revision.description
65
+ self.category_id = revision.category_id
64
66
  self.slug = revision.slug
65
67
 
66
68
  save!
@@ -18,6 +18,7 @@ module Landable
18
18
  self.name = template.name
19
19
  self.body = template.body
20
20
  self.description = template.description
21
+ self.category_id = template.category_id
21
22
  self.slug = template.slug
22
23
  end
23
24
 
@@ -4,9 +4,7 @@ module Landable
4
4
  page = resource
5
5
 
6
6
  case page.status_code
7
- when 200 then render text: RenderService.call(page, preview: options[:preview], responder: self),
8
- content_type: page.content_type,
9
- layout: (page.theme.try(:file) || false)
7
+ when 200 then render text: RenderService.call(page, preview: options[:preview], responder: self), content_type: page.content_type, layout: (page.theme.try(:file) || false)
10
8
  when 301, 302 then redirect_to page.redirect_url, status: page.status_code
11
9
  else fail page.error
12
10
  end
@@ -1,6 +1,6 @@
1
1
  module Landable
2
2
  class AccessTokenSerializer < ActiveModel::Serializer
3
- attributes :id, :expires_at
3
+ attributes :id, :expires_at, :permissions
4
4
  has_one :author, embed: :object
5
5
  end
6
6
  end
@@ -1,5 +1,5 @@
1
1
  module Landable
2
2
  class AuthorSerializer < ActiveModel::Serializer
3
- attributes :id, :username, :email, :first_name, :last_name
3
+ attributes :id, :username, :email, :first_name, :last_name, :can_read, :can_edit, :can_publish
4
4
  end
5
5
  end
@@ -12,5 +12,10 @@ module Landable
12
12
  has_one :published_revision
13
13
 
14
14
  has_many :pages
15
+ has_one :category
16
+
17
+ def category
18
+ object.category || Landable::Category.where(name: 'Uncategorized').first
19
+ end
15
20
  end
16
21
  end
@@ -37,7 +37,7 @@ module Landable
37
37
 
38
38
  def echo_author(username)
39
39
  { username: username, email: "#{username}@example.com",
40
- first_name: 'Trogdor', last_name: 'McBurninator' }
40
+ first_name: 'Trogdor', last_name: 'McBurninator', groups: ['Publisher'] }
41
41
  end
42
42
  end
43
43
  end
@@ -25,8 +25,8 @@ class CreateLandableSchema < Landable::Migration
25
25
 
26
26
  create_table "#{Landable.configuration.database_schema_prefix}landable.status_codes", id: :uuid, primary_key: :status_code_id do |t|
27
27
  t.uuid :status_code_category_id, null: false
28
- t.integer :code, null: false, limit: 2 # Creates as smallint
29
- t.text :description, null: false
28
+ t.integer :code, null: false, limit: 2 # Creates as smallint
29
+ t.text :description, null: false
30
30
  end
31
31
 
32
32
  execute "CREATE UNIQUE INDEX #{Landable.configuration.database_schema_prefix}landable_status_codes__u_code ON #{Landable.configuration.database_schema_prefix}landable.status_codes(code)"
@@ -56,7 +56,7 @@ class CreateLandableSchema < Landable::Migration
56
56
  t.text :body, null: false
57
57
  t.text :description, null: false
58
58
  t.text :thumbnail_url
59
- t.boolean :is_layout, null: false, default: false
59
+ t.boolean :is_layout, null: false, default: false
60
60
  t.timestamps
61
61
  end
62
62
 
@@ -123,7 +123,7 @@ class CreateLandableSchema < Landable::Migration
123
123
  ## access_tokens
124
124
 
125
125
  create_table "#{Landable.configuration.database_schema_prefix}landable.access_tokens", id: :uuid, primary_key: :access_token_id do |t|
126
- t.uuid :author_id, null: false
126
+ t.uuid :author_id, null: false
127
127
  t.timestamp :expires_at, null: false
128
128
  t.timestamps
129
129
  end
@@ -235,8 +235,8 @@ class CreateLandableSchema < Landable::Migration
235
235
  ## asset associations table
236
236
 
237
237
  create_table "#{Landable.configuration.database_schema_prefix}landable.page_assets", id: :uuid, primary_key: :page_asset_id do |t|
238
- t.uuid :page_id, null: false
239
- t.uuid :asset_id, null: false
238
+ t.uuid :page_id, null: false
239
+ t.uuid :asset_id, null: false
240
240
  end
241
241
 
242
242
  execute "CREATE UNIQUE INDEX #{Landable.configuration.database_schema_prefix}landable_page_assets__u_page_id_asset_id ON #{Landable.configuration.database_schema_prefix}landable.page_assets (page_id, asset_id)"
@@ -244,8 +244,8 @@ class CreateLandableSchema < Landable::Migration
244
244
  execute "ALTER TABLE #{Landable.configuration.database_schema_prefix}landable.page_assets ADD CONSTRAINT asset_id_fk FOREIGN KEY (asset_id) REFERENCES #{Landable.configuration.database_schema_prefix}landable.assets(asset_id)"
245
245
 
246
246
  create_table "#{Landable.configuration.database_schema_prefix}landable.page_revision_assets", id: :uuid, primary_key: :page_revision_asset_id do |t|
247
- t.uuid :page_revision_id, null: false
248
- t.uuid :asset_id, null: false
247
+ t.uuid :page_revision_id, null: false
248
+ t.uuid :asset_id, null: false
249
249
  end
250
250
 
251
251
  execute "CREATE UNIQUE INDEX #{Landable.configuration.database_schema_prefix}landable_page_revision_assets__u_page_revision_id_asset_id ON #{Landable.configuration.database_schema_prefix}landable.page_revision_assets (page_revision_id, asset_id)"
@@ -253,8 +253,8 @@ class CreateLandableSchema < Landable::Migration
253
253
  execute "ALTER TABLE #{Landable.configuration.database_schema_prefix}landable.page_revision_assets ADD CONSTRAINT asset_id_fk FOREIGN KEY (asset_id) REFERENCES #{Landable.configuration.database_schema_prefix}landable.assets(asset_id)"
254
254
 
255
255
  create_table "#{Landable.configuration.database_schema_prefix}landable.theme_assets", id: :uuid, primary_key: :theme_asset_id do |t|
256
- t.uuid :theme_id, null: false
257
- t.uuid :asset_id, null: false
256
+ t.uuid :theme_id, null: false
257
+ t.uuid :asset_id, null: false
258
258
  end
259
259
 
260
260
  execute "CREATE UNIQUE INDEX #{Landable.configuration.database_schema_prefix}landable_theme_assets__u_theme_id_asset_id ON #{Landable.configuration.database_schema_prefix}landable.theme_assets (theme_id, asset_id)"
@@ -0,0 +1,6 @@
1
+ class AddPermissionsToAccessTokens < ActiveRecord::Migration
2
+ def change
3
+ enable_extension 'hstore' unless extension_enabled?('hstore')
4
+ add_column "#{Landable.configuration.database_schema_prefix}landable.access_tokens", :permissions, :hstore
5
+ end
6
+ end
@@ -0,0 +1,13 @@
1
+ class AddCategoryColumnToTemplates < ActiveRecord::Migration
2
+ def change
3
+ schema_name = "#{Landable.configuration.database_schema_prefix}landable"
4
+
5
+ # add category_id to our templates table.
6
+ add_column "#{schema_name}.templates", :category_id, :uuid
7
+ execute "ALTER TABLE #{schema_name}.templates ADD FOREIGN KEY (category_id) REFERENCES #{schema_name}.categories(category_id)"
8
+
9
+ # add category_id to our template_revisions table.
10
+ add_column "#{schema_name}.template_revisions", :category_id, :uuid
11
+ execute "ALTER TABLE #{schema_name}.template_revisions ADD FOREIGN KEY (category_id) REFERENCES #{schema_name}.categories(category_id)"
12
+ end
13
+ end
@@ -17,6 +17,10 @@
17
17
 
18
18
  "author": {
19
19
  "$ref": "author.json"
20
+ },
21
+
22
+ "permissions": {
23
+ "$ref": "permissions.json"
20
24
  }
21
25
  }
22
26
  }
@@ -25,6 +25,18 @@
25
25
 
26
26
  "last_name": {
27
27
  "type": "string"
28
+ },
29
+
30
+ "can_read": {
31
+ "type": "boolean"
32
+ },
33
+
34
+ "can_edit": {
35
+ "type": "boolean"
36
+ },
37
+
38
+ "can_publish": {
39
+ "type": "boolean"
28
40
  }
29
41
  }
30
42
  }
@@ -0,0 +1,21 @@
1
+ {
2
+ "title": "Permissions",
3
+ "description": "Permissions for authors",
4
+ "type": "object",
5
+ "additionalProperties": false,
6
+ "required": ["read", "edit", "publish"],
7
+
8
+ "properties": {
9
+ "read": {
10
+ "type": "string"
11
+ },
12
+
13
+ "edit": {
14
+ "type": "string"
15
+ },
16
+
17
+ "publish": {
18
+ "type": "string"
19
+ }
20
+ }
21
+ }
@@ -47,6 +47,10 @@
47
47
  "items": { "$ref": "uuid.json" }
48
48
  },
49
49
 
50
+ "category_id": {
51
+ "type": [{ "$ref": "uuid.json" }, "null"]
52
+ },
53
+
50
54
  "thumbnail_url": {
51
55
  "type": ["string", "null"]
52
56
  },
@@ -18,6 +18,10 @@
18
18
  "type": "string"
19
19
  },
20
20
 
21
+ "category_id": {
22
+ "type": "uuid.json"
23
+ },
24
+
21
25
  "notes": {
22
26
  "type": ["string", "null"]
23
27
  },
@@ -2,7 +2,7 @@
2
2
  Feature: Access Tokens API
3
3
 
4
4
  Scenario: Responding with a fresh access token
5
- Given an author "someone"
5
+ Given an author "someone" without access tokens
6
6
  And "someone" has an unexpired access token
7
7
  When I POST to "/api/access_tokens" with:
8
8
  """
@@ -29,7 +29,7 @@ Feature: Access Tokens API
29
29
  And the author "someone" should have 1 access token
30
30
 
31
31
  Scenario: Reusing a pre-existing author record
32
- Given an author "someone"
32
+ Given an author "someone" without access tokens
33
33
  When I POST to "/api/access_tokens" with:
34
34
  """
35
35
  { "access_token": { "username": "someone", "password": "anything" } }
@@ -15,7 +15,7 @@ module Landable
15
15
  end
16
16
 
17
17
  def current_author
18
- @current_author ||= create :author
18
+ @current_author ||= create :author_without_access_tokens
19
19
  end
20
20
 
21
21
  def current_access_token
@@ -46,11 +46,11 @@ Before '@api', '~@no-api-auth' do
46
46
  end
47
47
 
48
48
  Given 'I accept HTML' do
49
- header 'Accept', 'text/html'
49
+ header 'Accept', 'text/html'
50
50
  end
51
51
 
52
52
  Given 'I accept JSON' do
53
- header 'Accept', 'application/json'
53
+ header 'Accept', 'application/json'
54
54
  end
55
55
 
56
56
  Given 'my API requests include a valid access token' do
@@ -18,6 +18,10 @@ Given(/^an author "([^"]+)"$/) do |username|
18
18
  create :author, username: username
19
19
  end
20
20
 
21
+ Given(/^an author "([^"]+)" without access tokens$/) do |username|
22
+ create :author_without_access_tokens, username: username
23
+ end
24
+
21
25
  Given '"$username" has an unexpired access token' do |username|
22
26
  create :access_token, author: Landable::Author.where(username: username).first!
23
27
  end
@@ -8,7 +8,7 @@ ENV['RAILS_ENV'] ||= 'test'
8
8
  require 'simplecov'
9
9
  SimpleCov.start 'rails'
10
10
 
11
- require File.expand_path('../../../spec/dummy/config/environment.rb', __FILE__)
11
+ require File.expand_path('../../../spec/dummy/config/environment.rb', __FILE__)
12
12
  ENV['RAILS_ROOT'] ||= File.dirname(__FILE__) + '../../../spec/dummy'
13
13
 
14
14
  require 'cucumber/rails'
@@ -31,19 +31,20 @@ Gem::Specification.new do |gem|
31
31
  gem.add_dependency 'rack-cors', '>= 0.2.7'
32
32
  gem.add_dependency 'active_model_serializers', '0.8.3'
33
33
  gem.add_dependency 'carrierwave'
34
- gem.add_dependency 'liquid', '~> 2.6.1'
34
+ gem.add_dependency 'liquid', '~> 2.6.1'
35
35
  gem.add_dependency 'fog'
36
36
  gem.add_dependency 'rest-client'
37
37
  gem.add_dependency 'builder'
38
38
  gem.add_dependency 'lookup_by', '> 0.4.0'
39
39
  gem.add_dependency 'highline'
40
+ gem.add_dependency 'figgy', '~> 1.1.0'
40
41
 
41
42
  gem.add_development_dependency 'pg'
42
43
  gem.add_development_dependency 'rspec-rails', '~> 2.14.2'
43
44
  gem.add_development_dependency 'factory_girl_rails', '~> 4.2.0'
44
45
  gem.add_development_dependency 'json-schema', '= 2.1.3'
45
46
  gem.add_development_dependency 'rack-schema'
46
- gem.add_development_dependency 'cucumber', '= 1.3.14'
47
+ gem.add_development_dependency 'cucumber', '= 1.3.14'
47
48
  gem.add_development_dependency 'database_cleaner'
48
49
  gem.add_development_dependency 'simplecov'
49
50
  gem.add_development_dependency 'valid_attribute'
@@ -20,10 +20,12 @@ module Landable
20
20
  autoload :Seeds, 'landable/seeds'
21
21
 
22
22
  def self.configuration
23
- @configuration ||= Configuration.new
23
+ @configuration ||= Configuration.new(@file_path)
24
24
  end
25
25
 
26
- def self.configure
26
+ def self.configure(path = nil)
27
+ @file_path = path
28
+
27
29
  yield configuration if block_given?
28
30
  configuration
29
31
  end
@@ -1,5 +1,7 @@
1
+ require 'figgy'
2
+
1
3
  module Landable
2
- class Configuration
4
+ class Configuration < HashWithIndifferentAccess
3
5
  attr_accessor :api_url, :public_url, :amqp_configuration, :sitemap_host
4
6
  attr_writer :api_namespace, :public_namespace
5
7
  attr_writer :api_host, :public_host
@@ -13,6 +15,24 @@ module Landable
13
15
  attr_writer :dnt_enabled, :amqp_event_mapping, :amqp_site_segment
14
16
  attr_writer :amqp_service_enabled, :amqp_messaging_service
15
17
 
18
+ def initialize(config_path = nil)
19
+ begin
20
+ # let's keep this feature optional. Not all apps
21
+ # will be using external configs for landable
22
+ app_config = Figgy.build do |config_data|
23
+ config_data.root = config_path
24
+
25
+ config_data.define_overlay :default, nil
26
+ config_data.define_overlay(:environment) { Rails.env }
27
+ end
28
+
29
+ # map our new configs into our local object.
30
+ config_keys(config_path).each do |key|
31
+ self[key] = app_config[key] unless app_config[key].nil?
32
+ end
33
+ end unless config_path.nil?
34
+ end
35
+
16
36
  def amqp_configuration
17
37
  @amqp_configuration ||= {}
18
38
  end
@@ -172,5 +192,42 @@ module Landable
172
192
  @autorun = true
173
193
  end
174
194
  end
195
+
196
+ private
197
+
198
+ DOTFILE_MATCHER_REGEXP = /^\.[[[:alnum:]]\.]*$/
199
+ EXPECTED_FILETYPES = ['yml', 'yaml', 'json']
200
+ EXPECTED_FILETYPES_REGEXP = /\.(#{ EXPECTED_FILETYPES.join('|') })\z/
201
+
202
+ def config_keys(base_path)
203
+ files = Dir.entries(base_path)
204
+ keys = []
205
+
206
+ files = remove_dotfiles(files)
207
+
208
+ files.each do |filename|
209
+ filename = File.join(base_path, filename)
210
+ new_key = filename_to_key(filename)
211
+
212
+ if File.file?(filename)
213
+ keys.push(new_key)
214
+ elsif File.directory?(filename) && new_key == Rails.env
215
+ # only folders matching our environment!
216
+ keys = keys.concat(config_keys(filename))
217
+ end
218
+ end
219
+
220
+ keys.uniq
221
+ end
222
+
223
+ def filename_to_key(filename)
224
+ File.basename(filename).sub(EXPECTED_FILETYPES_REGEXP, '')
225
+ end
226
+
227
+ def remove_dotfiles(filelist)
228
+ filelist.delete_if do |filename|
229
+ !DOTFILE_MATCHER_REGEXP.match(File.basename(filename)).nil?
230
+ end
231
+ end
175
232
  end
176
233
  end
@@ -29,29 +29,29 @@ module Landable
29
29
  ATTRIBUTION_KEYS = TRACKING_PARAMS.except('click_id').keys
30
30
 
31
31
  TRACKING_PARAMS_TRANSFORM = {
32
- 'ad_type' => { 'pe' => 'product_extensions',
33
- 'pla' => 'product_listing' },
32
+ 'ad_type' => { 'pe' => 'product_extensions',
33
+ 'pla' => 'product_listing' },
34
34
 
35
35
  'bid_match_type' => { 'bb' => 'bidded broad',
36
36
  'bc' => 'bidded content',
37
37
  'be' => 'bidded exact',
38
38
  'bp' => 'bidded phrase' },
39
39
 
40
- 'device_type' => { 'c' => 'computer',
41
- 'm' => 'mobile',
42
- 't' => 'tablet' },
43
-
44
- 'match_type' => { 'b' => 'broad',
45
- 'c' => 'content',
46
- 'e' => 'exact',
47
- 'p' => 'phrase',
48
- 'std' => 'standard',
49
- 'adv' => 'advanced',
50
- 'cnt' => 'content' },
51
-
52
- 'network' => { 'g' => 'google_search',
53
- 's' => 'search_partner',
54
- 'd' => 'display_network' }
40
+ 'device_type' => { 'c' => 'computer',
41
+ 'm' => 'mobile',
42
+ 't' => 'tablet' },
43
+
44
+ 'match_type' => { 'b' => 'broad',
45
+ 'c' => 'content',
46
+ 'e' => 'exact',
47
+ 'p' => 'phrase',
48
+ 'std' => 'standard',
49
+ 'adv' => 'advanced',
50
+ 'cnt' => 'content' },
51
+
52
+ 'network' => { 'g' => 'google_search',
53
+ 's' => 'search_partner',
54
+ 'd' => 'display_network' }
55
55
  }.freeze
56
56
 
57
57
  UUID_REGEX = /\A\h{8}-\h{4}-\h{4}-\h{4}-\h{12}\Z/
@@ -1,8 +1,8 @@
1
1
  module Landable
2
2
  module VERSION
3
3
  MAJOR = 1
4
- MINOR = 13
5
- PATCH = 2
4
+ MINOR = 14
5
+ PATCH = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, PATCH].compact.join('.')
8
8
  end
@@ -13,6 +13,12 @@ module Landable
13
13
  end
14
14
 
15
15
  it 'renders the page as JSON' do
16
+ # pending 'This broken test requires refactoring.'
17
+ # this test cannot possibly pass given the state of the configuration object and controller.
18
+ # * The controller returns a json version of an object that wasn't initially even a Hash.
19
+ # * The new configuration object inherits Hash but 'audit_flags' gets lost in conversion
20
+ # to a JSON object because it is a static attribute of the Landable.configuration object.
21
+
16
22
  make_request
17
23
  # defined in Landable Dummy Initalizer
18
24
  last_json['configurations'][0]['audit_flags'].should eq %w(loans apr)
@@ -162,7 +162,7 @@ describe Landable::ApiController, json: true do
162
162
  controller.instance_variable_set :@resource, resource
163
163
  end
164
164
 
165
- let(:resource) { build :author }
165
+ let(:resource) { create :author }
166
166
 
167
167
  it 'should set X-Landable-Media-Type' do
168
168
  get :responder
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
- APP_PATH = File.expand_path('../../config/application', __FILE__)
2
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
3
3
  require_relative '../config/boot'
4
4
  require 'rails/commands'
@@ -1,4 +1,4 @@
1
1
  # This file is used by Rack-based servers to start the application.
2
2
 
3
- require ::File.expand_path('../config/environment', __FILE__)
3
+ require ::File.expand_path('../config/environment', __FILE__)
4
4
  run Rails.application
@@ -13,7 +13,7 @@ Dummy::Application.configure do
13
13
  config.eager_load = false
14
14
 
15
15
  # Configure static asset server for tests with Cache-Control for performance.
16
- config.serve_static_files = true
16
+ config.serve_static_files = true
17
17
  config.static_cache_control = 'public, max-age=3600'
18
18
 
19
19
  # Show full error reports and disable caching.
@@ -1,7 +1,7 @@
1
1
  require 'landable'
2
2
  require Rails.root.join('lib', 'bunny_messaging_service.rb')
3
3
 
4
- Landable.configure do |config|
4
+ Landable.configure(File.join(File.dirname(__FILE__), '..', '..', 'etc')) do |config|
5
5
  config.api_namespace = '/api'
6
6
  config.cors.origins = ['http://cors.test']
7
7
 
@@ -0,0 +1 @@
1
+ ['loans', 'apr']
@@ -0,0 +1,17 @@
1
+ :host: adds.enova.com
2
+ :port: 636
3
+ :encryption: :simple_tls
4
+ :base: 'DC=enova,DC=com'
5
+ :permissions:
6
+ Read-Only:
7
+ read: 'true'
8
+ edit: 'false'
9
+ publish: 'false'
10
+ Editor:
11
+ read: 'true'
12
+ edit: 'true'
13
+ publish: 'false'
14
+ Publisher:
15
+ read: 'true'
16
+ edit: 'true'
17
+ publish: 'true'
@@ -0,0 +1,85 @@
1
+ enabled: true
2
+ rescue_errors: true
3
+ brand_code: 'US'
4
+
5
+ clients:
6
+ cnuapp_proxy:
7
+ domain: "www-us-trogdor-cnuapp.dev.enova.com" # For Trogdor AWS box
8
+ secure: true
9
+ ping: true
10
+ session_cookies:
11
+ - cnuwww_session_id
12
+ - _us_account_home_session
13
+ cnuapp:
14
+ domain: "api-us-trogdor-cnuapp.dev.enova.com" # For Trogdor AWS box
15
+ secure: true
16
+ identity:
17
+ default_base_url: "http://portal-us-trogdor-cnuapp.dev.enova.com" # For Trogdor AWS box
18
+ portfolio:
19
+ default_base_url: "http://portal-us-trogdor-cnuapp.dev.enova.com" # For Trogdor AWS box
20
+
21
+ features:
22
+ registration:
23
+ enabled: true
24
+ redirects:
25
+ new:
26
+ "/registration-step1.html": "/registration"
27
+ "/secure/customers/new2": "/registration/step2"
28
+ old:
29
+ "/registration": "/registration-step1.html"
30
+ "/registration/step2": "/secure/customers/new2"
31
+
32
+ filter_errors:
33
+ "is invalid": "Unfortunately there seems to be an issue with the information you've provided. For assistance, please contact our customer support team at"
34
+ "Could not save customer. Potential fraud; lead email does not match form email": "Unfortunately there seems to be an issue with the information you've provided. For assistance, please contact our customer support team at"
35
+ "Could not save customer": "Unfortunately there seems to be an issue with the information you've provided. For assistance, please contact our customer support team at"
36
+
37
+ pings:
38
+ "/registration": "/registration-step1.html"
39
+ "/registration/step2": "/secure/customers/new2"
40
+
41
+ registration:
42
+ title_options:
43
+ mr: Mr.
44
+ mrs: Mrs.
45
+ miss: Miss
46
+ ms: Ms.
47
+ other: Other
48
+
49
+ payment_frequency_options:
50
+ weekly: Weekly (e.g. Every Friday)
51
+ biweekly: Biweekly (e.g. Every other Friday)
52
+ twice_monthly: Twice monthly (ex. The 1st and 15th of each month)
53
+ specific_date: Monthly on a specific date (ex. 1st of the Month)
54
+ last_working_day: Monthly last day (ex. Last business day or last calendar day)
55
+ specific_day: Monthly on a specific weekday (ex. 3rd Wednesday of each month)
56
+ last_day: Monthly last weekday (ex. Last Tuesday of each month)
57
+
58
+ repayment_options:
59
+ ach_debit: bank_account_ach
60
+ remotely_created_checks: ecld
61
+
62
+ acquisition_source_options:
63
+ - TV Advert
64
+ - Radio Advert
65
+ - Moneysupermarket, Gocompare or Similar
66
+ - Google Results
67
+ - Friends/Family
68
+ - Facebook/Twitter
69
+ - Online Image/Video Ad
70
+ - Football Stadium
71
+ - Email
72
+ - SMS
73
+ - Other...
74
+
75
+ website_message:
76
+ header: "message"
77
+ body: "Due to the upcoming banking holiday, all requests to make changes for loans due on <span class='nobreak'>Nov 28 2014</span> must be received by 3:00 PM CST on <span class='nobreak'>Nov 25 2014.</span><br />All loans approved on Nov 26 2014 will generally be funded on Nov 28 2014."
78
+ start_date: 2014-11-20 00:00:01 -0600
79
+ end_date: 2014-11-27 23:59:59 -0600
80
+
81
+ ldap:
82
+ host: ldap.cashnetusa.com
83
+ port: 389
84
+ ssl: start_tls
85
+ base: 'ou=user,ou=jabber,ou=auth,dc=cashnetusa,dc=com'
@@ -0,0 +1,12 @@
1
+ # Add extra URLs which aren't in publicist but should be included in sitemap.xml
2
+ # This file is replaced from cnu_config overlay so make changes there
3
+
4
+ - /
5
+ - /blog
6
+ - /forgot_password.html
7
+ - /login
8
+ - /new-customer-registration
9
+ - /newsroom
10
+ - /registration-step1.html
11
+ - /secure/auth/forgot_email
12
+ - /secure/auth/forgot_password
@@ -1,12 +1,28 @@
1
1
  FactoryGirl.define do
2
+ factory :access_token, class: 'Landable::AccessToken' do
3
+ association :author, strategy: :create
4
+ permissions { { 'read' => 'true', 'edit' => 'true', 'publish' => 'true' } }
5
+ end
6
+
2
7
  factory :author, class: 'Landable::Author' do
3
8
  sequence(:username) { |n| "trogdor#{n}" }
4
9
  sequence(:email) { |n| "trogdor#{n}@example.com" }
5
10
  first_name 'Marley'
6
11
  last_name 'Pants'
12
+
13
+ ignore do
14
+ tokens_count 1
15
+ end
16
+
17
+ after(:create) do |author, evaluator|
18
+ create_list(:access_token, evaluator.tokens_count, author: author)
19
+ end
7
20
  end
8
21
 
9
- factory :access_token, class: 'Landable::AccessToken' do
10
- association :author, strategy: :build
22
+ factory :author_without_access_tokens, class: 'Landable::Author' do
23
+ sequence(:username) { |n| "trogdor#{n}" }
24
+ sequence(:email) { |n| "trogdor#{n}@example.com" }
25
+ first_name 'Marley'
26
+ last_name 'Pants'
11
27
  end
12
28
  end
@@ -6,7 +6,8 @@ module Landable
6
6
 
7
7
  it 'generates an expiration timestamp before creation' do
8
8
  author = create :author
9
- token = AccessToken.create!(author: author)
9
+ permissions = { 'read' => 'true', 'edit' => 'true', 'publish' => 'true' }
10
+ token = AccessToken.create!(author: author, permissions: permissions)
10
11
  expect(token.expires_at).not_to be_nil
11
12
  end
12
13
  end
@@ -365,7 +365,7 @@ module Landable
365
365
 
366
366
  describe '::by_path' do
367
367
  it 'returns first page with path name' do
368
- page = create :page, path: '/seo'
368
+ page = create :page, path: '/seo'
369
369
  Landable::Page.by_path('/seo').should eq page
370
370
  end
371
371
  end
@@ -4,7 +4,7 @@ describe Landable::AuthenticationService do
4
4
  let(:simple_auth) do
5
5
  proc do |username, password|
6
6
  if username == 'simple' && password == 'authenticator'
7
- { username: 'simple', email: 'simple@example.com', first_name: 'Simple', last_name: 'Ton' }
7
+ { username: 'simple', email: 'simple@example.com', first_name: 'Simple', last_name: 'Ton', groups: ['CreditMe Read-only', 'QuickQuid Editor', 'Netcredit Publisher'] }
8
8
  end
9
9
  end
10
10
  end
@@ -45,7 +45,7 @@ module Landable
45
45
  end
46
46
 
47
47
  context 'for a redirect' do
48
- let(:page) { build :page, :redirect }
48
+ let(:page) { build :page, :redirect }
49
49
 
50
50
  context 'previewing' do
51
51
  let(:rendered) { render(preview: true) }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: landable
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.13.2
4
+ version: 1.14.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-06-30 00:00:00.000000000 Z
12
+ date: 2015-08-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -171,6 +171,22 @@ dependencies:
171
171
  - - ! '>='
172
172
  - !ruby/object:Gem::Version
173
173
  version: '0'
174
+ - !ruby/object:Gem::Dependency
175
+ name: figgy
176
+ requirement: !ruby/object:Gem::Requirement
177
+ none: false
178
+ requirements:
179
+ - - ~>
180
+ - !ruby/object:Gem::Version
181
+ version: 1.1.0
182
+ type: :runtime
183
+ prerelease: false
184
+ version_requirements: !ruby/object:Gem::Requirement
185
+ none: false
186
+ requirements:
187
+ - - ~>
188
+ - !ruby/object:Gem::Version
189
+ version: 1.1.0
174
190
  - !ruby/object:Gem::Dependency
175
191
  name: pg
176
192
  requirement: !ruby/object:Gem::Requirement
@@ -510,6 +526,8 @@ files:
510
526
  - db/migrate/20140602213937_path_response_time_view.rb
511
527
  - db/migrate/20141211200012_add_page_name_to_page.rb
512
528
  - db/migrate/20141217171816_add_counter_for_themes_pages.rb
529
+ - db/migrate/20150610999999_add_permissions_to_access_tokens.rb
530
+ - db/migrate/20150728195345_add_category_column_to_templates.rb
513
531
  - db/test/landable.access_tokens.sql
514
532
  - db/test/landable.assets.sql
515
533
  - db/test/landable.authors.sql
@@ -526,6 +544,7 @@ files:
526
544
  - doc/schema/directory.json
527
545
  - doc/schema/page.json
528
546
  - doc/schema/page_revision.json
547
+ - doc/schema/permissions.json
529
548
  - doc/schema/template.json
530
549
  - doc/schema/template_revision.json
531
550
  - doc/schema/theme.json
@@ -664,6 +683,10 @@ files:
664
683
  - spec/dummy/config/routes.rb
665
684
  - spec/dummy/db/.keep
666
685
  - spec/dummy/db/structure.sql
686
+ - spec/dummy/etc/audit_flags.yml
687
+ - spec/dummy/etc/ldap.yml
688
+ - spec/dummy/etc/sandiego.yml
689
+ - spec/dummy/etc/sitemap_extra.yml
667
690
  - spec/dummy/lib/assets/.keep
668
691
  - spec/dummy/lib/bunny_messaging_service.rb
669
692
  - spec/dummy/log/.keep
@@ -730,7 +753,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
730
753
  version: '0'
731
754
  segments:
732
755
  - 0
733
- hash: 953659550368963873
756
+ hash: -154968153976918238
734
757
  required_rubygems_version: !ruby/object:Gem::Requirement
735
758
  none: false
736
759
  requirements:
@@ -739,7 +762,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
739
762
  version: '0'
740
763
  segments:
741
764
  - 0
742
- hash: 953659550368963873
765
+ hash: -154968153976918238
743
766
  requirements: []
744
767
  rubyforge_project:
745
768
  rubygems_version: 1.8.23
@@ -832,6 +855,10 @@ test_files:
832
855
  - spec/dummy/config/routes.rb
833
856
  - spec/dummy/db/.keep
834
857
  - spec/dummy/db/structure.sql
858
+ - spec/dummy/etc/audit_flags.yml
859
+ - spec/dummy/etc/ldap.yml
860
+ - spec/dummy/etc/sandiego.yml
861
+ - spec/dummy/etc/sitemap_extra.yml
835
862
  - spec/dummy/lib/assets/.keep
836
863
  - spec/dummy/lib/bunny_messaging_service.rb
837
864
  - spec/dummy/log/.keep