landable 1.13.2 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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