hydra-access-controls 0.0.2 → 0.0.3
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.
- data/.gitignore +1 -0
- data/.gitmodules +3 -0
- data/.rspec +2 -0
- data/Gemfile +6 -0
- data/README.textile +100 -0
- data/Rakefile +6 -3
- data/config/fedora.yml +8 -0
- data/config/jetty.yml +5 -0
- data/config/solr.yml +6 -0
- data/hydra-access-controls.gemspec +3 -2
- data/lib/ability.rb +1 -0
- data/lib/hydra-access-controls.rb +10 -0
- data/lib/hydra-access-controls/version.rb +1 -1
- data/lib/hydra/ability.rb +14 -13
- data/lib/hydra/access_controls_enforcement.rb +27 -16
- data/lib/hydra/admin_policy.rb +81 -0
- data/lib/hydra/datastream.rb +1 -0
- data/lib/hydra/datastream/inheritable_rights_metadata.rb +22 -0
- data/lib/hydra/policy_aware_ability.rb +128 -0
- data/lib/hydra/policy_aware_access_controls_enforcement.rb +70 -0
- data/lib/hydra/role_mapper_behavior.rb +16 -2
- data/lib/hydra/user.rb +42 -0
- data/lib/tasks/hydra-access-controls.rake +18 -0
- data/lib/tasks/hydra_jetty.rake +55 -0
- data/solr_conf/conf/schema.xml +124 -0
- data/solr_conf/conf/solrconfig.xml +329 -0
- data/solr_conf/solr.xml +35 -0
- data/spec/factories.rb +101 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/support/blacklight.rb +7 -0
- data/spec/support/config/solr.yml +4 -0
- data/spec/support/mods_asset.rb +4 -1
- data/spec/support/rails.rb +10 -0
- data/spec/support/solr_document.rb +13 -0
- data/spec/support/user.rb +32 -0
- data/spec/unit/ability_spec.rb +338 -56
- data/spec/unit/access_controls_enforcement_spec.rb +180 -0
- data/spec/unit/admin_policy_spec.rb +89 -0
- data/spec/unit/inheritable_rights_metadata_spec.rb +66 -0
- data/spec/unit/policy_aware_ability_spec.rb +92 -0
- data/spec/unit/policy_aware_access_controls_enforcement_spec.rb +109 -0
- metadata +59 -4
- data/README.md +0 -29
data/lib/hydra/datastream.rb
CHANGED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'active_support/core_ext/string'
|
2
|
+
module Hydra
|
3
|
+
module Datastream
|
4
|
+
# Implements Hydra RightsMetadata XML terminology for asserting access permissions
|
5
|
+
class InheritableRightsMetadata < Hydra::Datastream::RightsMetadata
|
6
|
+
|
7
|
+
@terminology = Hydra::Datastream::RightsMetadata.terminology
|
8
|
+
|
9
|
+
def to_solr(solr_doc=Hash.new)
|
10
|
+
solr_doc["inheritable_access_t"] = access.machine.group.val + access.machine.person.val
|
11
|
+
solr_doc["inheritable_discover_access_group_t"] = discover_access.machine.group
|
12
|
+
solr_doc["inheritable_discover_access_person_t"] = discover_access.machine.person
|
13
|
+
solr_doc["inheritable_read_access_group_t"] = read_access.machine.group
|
14
|
+
solr_doc["inheritable_read_access_person_t"] = read_access.machine.person
|
15
|
+
solr_doc["inheritable_edit_access_group_t"] = edit_access.machine.group
|
16
|
+
solr_doc["inheritable_edit_access_person_t"] = edit_access.machine.person
|
17
|
+
solr_doc["inheritable_embargo_release_date_dt"] = embargo_release_date
|
18
|
+
return solr_doc
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# Repeats access controls evaluation methods, but checks against a governing "Policy" object (or "Collection" object) that provides inherited access controls.
|
2
|
+
module Hydra::PolicyAwareAbility
|
3
|
+
|
4
|
+
# Extends Hydra::Ability.test_edit to try policy controls if object-level controls deny access
|
5
|
+
def test_edit(pid, user, session)
|
6
|
+
result = super
|
7
|
+
if result
|
8
|
+
return result
|
9
|
+
else
|
10
|
+
return test_edit_from_policy(pid, user, session)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Extends Hydra::Ability.test_read to try policy controls if object-level controls deny access
|
15
|
+
def test_read(pid, user, session)
|
16
|
+
result = super
|
17
|
+
if result
|
18
|
+
return result
|
19
|
+
else
|
20
|
+
return test_read_from_policy(pid, user, session)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns the pid of policy object (is_governed_by) for the specified object
|
25
|
+
# Assumes that the policy object is associated by an is_governed_by relationship (Whis is stored as "is_governed_by_s" in object's solr document)
|
26
|
+
# Returns nil if no policy associated with the object
|
27
|
+
def policy_pid_for(object_pid)
|
28
|
+
return @policy_pid if @policy_pid
|
29
|
+
solr_result = ActiveFedora::Base.find_with_conditions({:id=>object_pid}, :fl=>'is_governed_by_s')
|
30
|
+
begin
|
31
|
+
@policy_pid = value_from_solr_field(solr_result, 'is_governed_by_s').first.gsub("info:fedora/", "")
|
32
|
+
rescue NoMethodError
|
33
|
+
@policy_pid = nil
|
34
|
+
end
|
35
|
+
return @policy_pid
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns the permissions solr document for policy_pid
|
39
|
+
# The document is stored in an instance variable, so calling this multiple times will only query solr once.
|
40
|
+
# To force reload, set @policy_permissions_solr_document to nil
|
41
|
+
def policy_permissions_doc(policy_pid)
|
42
|
+
return @policy_permissions_solr_document if @policy_permissions_solr_document
|
43
|
+
response, @policy_permissions_solr_document = get_permissions_solr_response_for_doc_id(policy_pid)
|
44
|
+
@policy_permissions_solr_document
|
45
|
+
end
|
46
|
+
|
47
|
+
# Tests whether the object's governing policy object grants edit access for the current user
|
48
|
+
def test_edit_from_policy(object_pid, user, session)
|
49
|
+
policy_pid = policy_pid_for(object_pid)
|
50
|
+
if policy_pid.nil?
|
51
|
+
return false
|
52
|
+
else
|
53
|
+
logger.debug("[CANCAN] -policy- Does the POLICY #{policy_pid} provide EDIT permissions for #{user_key(user)}?")
|
54
|
+
group_intersection = user_groups(user, session) & edit_groups_from_policy( policy_pid )
|
55
|
+
result = !group_intersection.empty? || edit_persons_from_policy( policy_pid ).include?(user_key(user))
|
56
|
+
logger.debug("[CANCAN] -policy- decision: #{result}")
|
57
|
+
return result
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Tests whether the object's governing policy object grants read access for the current user
|
62
|
+
def test_read_from_policy(object_pid, user, session)
|
63
|
+
policy_pid = policy_pid_for(object_pid)
|
64
|
+
if policy_pid.nil?
|
65
|
+
return false
|
66
|
+
else
|
67
|
+
logger.debug("[CANCAN] -policy- Does the POLICY #{policy_pid} provide READ permissions for #{user_key(user)}?")
|
68
|
+
group_intersection = user_groups(user, session) & read_groups_from_policy( policy_pid )
|
69
|
+
result = !group_intersection.empty? || read_persons_from_policy( policy_pid ).include?(user_key(user))
|
70
|
+
logger.debug("[CANCAN] -policy- decision: #{result}")
|
71
|
+
result
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns the list of groups granted edit access by the policy object identified by policy_pid
|
76
|
+
def edit_groups_from_policy(policy_pid)
|
77
|
+
policy_permissions = policy_permissions_doc(policy_pid)
|
78
|
+
edit_group_field = Hydra.config[:permissions][:inheritable][:edit][:group]
|
79
|
+
eg = ((policy_permissions == nil || policy_permissions.fetch(edit_group_field,nil) == nil) ? [] : policy_permissions.fetch(edit_group_field,nil))
|
80
|
+
logger.debug("[CANCAN] -policy- edit_groups: #{eg.inspect}")
|
81
|
+
return eg
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns the list of groups granted read access by the policy object identified by policy_pid
|
85
|
+
# Note: edit implies read, so read_groups is the union of edit and read groups
|
86
|
+
def read_groups_from_policy(policy_pid)
|
87
|
+
policy_permissions = policy_permissions_doc(policy_pid)
|
88
|
+
read_group_field = Hydra.config[:permissions][:inheritable][:read][:group]
|
89
|
+
rg = edit_groups_from_policy(policy_pid) | ((policy_permissions == nil || policy_permissions.fetch(read_group_field,nil) == nil) ? [] : policy_permissions.fetch(read_group_field,nil))
|
90
|
+
logger.debug("[CANCAN] -policy- read_groups: #{rg.inspect}")
|
91
|
+
return rg
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns the list of individuals granted edit access by the policy object identified by policy_pid
|
95
|
+
def edit_persons_from_policy(policy_pid)
|
96
|
+
policy_permissions = policy_permissions_doc(policy_pid)
|
97
|
+
edit_person_field = Hydra.config[:permissions][:inheritable][:edit][:individual]
|
98
|
+
ep = ((policy_permissions == nil || policy_permissions.fetch(edit_person_field,nil) == nil) ? [] : policy_permissions.fetch(edit_person_field,nil))
|
99
|
+
logger.debug("[CANCAN] -policy- edit_persons: #{ep.inspect}")
|
100
|
+
return ep
|
101
|
+
end
|
102
|
+
|
103
|
+
# Returns the list of individuals granted read access by the policy object identified by policy_pid
|
104
|
+
# Noate: edit implies read, so read_persons is the union of edit and read persons
|
105
|
+
def read_persons_from_policy(policy_pid)
|
106
|
+
policy_permissions = policy_permissions_doc(policy_pid)
|
107
|
+
read_individual_field = Hydra.config[:permissions][:inheritable][:read][:individual]
|
108
|
+
rp = edit_persons_from_policy(policy_pid) | ((policy_permissions == nil || policy_permissions.fetch(read_individual_field,nil) == nil) ? [] : policy_permissions.fetch(read_individual_field,nil))
|
109
|
+
logger.debug("[CANCAN] -policy- read_persons: #{rp.inspect}")
|
110
|
+
return rp
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
# Grabs the value of field_name from solr_result
|
116
|
+
# @example
|
117
|
+
# solr_result = Multiresimage.find_with_conditions({:id=>object_pid}, :fl=>'is_governed_by_s')
|
118
|
+
# value_from_solr_field(solr_result, 'is_governed_by_s')
|
119
|
+
# => ["info:fedora/changeme:2278"]
|
120
|
+
def value_from_solr_field(solr_result, field_name)
|
121
|
+
field_from_result = solr_result.select {|x| x.has_key?(field_name)}.first
|
122
|
+
if field_from_result.nil?
|
123
|
+
return nil
|
124
|
+
else
|
125
|
+
return field_from_result[field_name]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# Repeats access controls evaluation methods, but checks against a governing "Policy" object (or "Collection" object) that provides inherited access controls.
|
2
|
+
module Hydra::PolicyAwareAccessControlsEnforcement
|
3
|
+
|
4
|
+
# Extends Hydra::AccessControlsEnforcement.apply_gated_discovery to reflect policy-provided access
|
5
|
+
# appends the result of policy_clauses into the :fq
|
6
|
+
def apply_gated_discovery(solr_parameters, user_parameters)
|
7
|
+
super
|
8
|
+
additional_clauses = policy_clauses
|
9
|
+
unless additional_clauses.nil? || additional_clauses.empty?
|
10
|
+
solr_parameters[:fq].first << " OR " + policy_clauses
|
11
|
+
logger.debug("POLICY-aware Solr parameters: #{ solr_parameters.inspect }")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# returns solr query for finding all objects whose policies grant discover access to current_user
|
16
|
+
def policy_clauses
|
17
|
+
policy_pids = policies_with_access
|
18
|
+
return nil if policy_pids.empty?
|
19
|
+
'(' + policy_pids.map {|pid| "is_governed_by_s:info\\:fedora/#{pid.gsub(/:/, '\\\\:')}"}.join(' OR ') + ')'
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
# find all the policies that grant discover/read/edit permissions to this user or any of it's groups
|
24
|
+
def policies_with_access
|
25
|
+
#### TODO -- Memoize this and put it in the session?
|
26
|
+
return [] unless current_user
|
27
|
+
user_access_filters = []
|
28
|
+
# Grant access based on user id & role
|
29
|
+
unless current_user.nil?
|
30
|
+
user_access_filters += apply_policy_role_permissions(discovery_permissions)
|
31
|
+
user_access_filters += apply_policy_individual_permissions(discovery_permissions)
|
32
|
+
end
|
33
|
+
result = policy_class.find_with_conditions( user_access_filters.join(" OR "), :fl => "id" )
|
34
|
+
logger.debug "get policies: #{result}\n\n"
|
35
|
+
result.map {|h| h['id']}
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def apply_policy_role_permissions(permission_types)
|
40
|
+
# for roles
|
41
|
+
user_access_filters = []
|
42
|
+
::RoleMapper.roles(user_key).each_with_index do |role, i|
|
43
|
+
discovery_permissions.each do |type|
|
44
|
+
user_access_filters << "inheritable_#{type}_access_group_t:#{role}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
user_access_filters
|
48
|
+
end
|
49
|
+
|
50
|
+
def apply_policy_individual_permissions(permission_types)
|
51
|
+
# for individual person access
|
52
|
+
user_access_filters = []
|
53
|
+
discovery_permissions.each do |type|
|
54
|
+
user_access_filters << "inheritable_#{type}_access_person_t:#{user_key}"
|
55
|
+
end
|
56
|
+
user_access_filters
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns the Model used for AdminPolicy objects.
|
60
|
+
# You can set this by overriding this method or setting Hydra.config[:permissions][:policy_class]
|
61
|
+
# Defults to Hydra::AdminPolicy
|
62
|
+
def policy_class
|
63
|
+
if Hydra.config[:permissions][:policy_class].nil?
|
64
|
+
return Hydra::AdminPolicy
|
65
|
+
else
|
66
|
+
return Hydra.config[:permissions][:policy_class]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -7,8 +7,21 @@ module Hydra::RoleMapperBehavior
|
|
7
7
|
def role_names
|
8
8
|
map.keys
|
9
9
|
end
|
10
|
-
|
11
|
-
|
10
|
+
|
11
|
+
#
|
12
|
+
# @param user_or_uid either the User object or user id
|
13
|
+
# If you pass in a nil User object (ie. user isn't logged in), or a uid that doesn't exist, it will return an empty array
|
14
|
+
def roles(user_or_uid)
|
15
|
+
if user_or_uid.kind_of?(String)
|
16
|
+
user = User.find_by_user_key(user_or_uid)
|
17
|
+
user_id = user_or_uid
|
18
|
+
elsif user_or_uid.kind_of?(User) && !user_or_uid.uid.nil?
|
19
|
+
user = user_or_uid
|
20
|
+
user_id = user.user_key
|
21
|
+
end
|
22
|
+
array = byname[user_id]||[]
|
23
|
+
array = array << 'registered' unless (user.nil? || user.new_record?)
|
24
|
+
array
|
12
25
|
end
|
13
26
|
|
14
27
|
def whois(r)
|
@@ -28,6 +41,7 @@ module Hydra::RoleMapperBehavior
|
|
28
41
|
memo
|
29
42
|
end
|
30
43
|
end
|
44
|
+
|
31
45
|
end
|
32
46
|
end
|
33
47
|
|
data/lib/hydra/user.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# Injects behaviors into User model so that it will work with Hydra Access Controls
|
2
|
+
# By default, this module assumes you are using the User model created by Blacklight, which uses Devise.
|
3
|
+
# To integrate your own User implementation into Hydra, override this Module or define your own User model in app/models/user.rb within your Hydra head.
|
4
|
+
require 'deprecation'
|
5
|
+
module Hydra::User
|
6
|
+
extend Deprecation
|
7
|
+
|
8
|
+
def self.included(klass)
|
9
|
+
# Other modules to auto-include
|
10
|
+
klass.extend(ClassMethods)
|
11
|
+
end
|
12
|
+
|
13
|
+
# This method should display the unique identifier for this user as defined by devise.
|
14
|
+
# The unique identifier is what access controls will be enforced against.
|
15
|
+
def user_key
|
16
|
+
send(Devise.authentication_keys.first)
|
17
|
+
end
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
# This method should find User objects using the user_key you've chosen.
|
21
|
+
# By default, uses the unique identifier specified in by devise authentication_keys (ie. find_by_id, or find_by_email).
|
22
|
+
# You must have that find method implemented on your user class, or must override find_by_user_key
|
23
|
+
def find_by_user_key(key)
|
24
|
+
self.send("find_by_#{Devise.authentication_keys.first}".to_sym, key)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# This method should display the unique identifier for this user
|
29
|
+
# the unique identifier is what access controls will be enforced against.
|
30
|
+
def unique_id
|
31
|
+
return to_s
|
32
|
+
end
|
33
|
+
deprecation_deprecate :unique_id
|
34
|
+
|
35
|
+
|
36
|
+
# For backwards compatibility with the Rails2 User models in Hydra/Blacklight
|
37
|
+
def login
|
38
|
+
return unique_id
|
39
|
+
end
|
40
|
+
deprecation_deprecate :login
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
namespace "hydra-access" do
|
2
|
+
desc "Run Continuous Integration"
|
3
|
+
task "ci" do
|
4
|
+
require 'jettywrapper'
|
5
|
+
jetty_params = Jettywrapper.load_config.merge(
|
6
|
+
{:jetty_home => File.expand_path(File.dirname(__FILE__) + '/../../jetty'),
|
7
|
+
:startup_wait => 15,
|
8
|
+
:jetty_port => ENV['TEST_JETTY_PORT'] || 8983
|
9
|
+
}
|
10
|
+
)
|
11
|
+
Rake::Task['jetty:config'].invoke
|
12
|
+
error = nil
|
13
|
+
error = Jettywrapper.wrap(jetty_params) do
|
14
|
+
Rake::Task['spec'].invoke
|
15
|
+
end
|
16
|
+
raise "test failures: #{error}" if error
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# re-using hydra_jetty.rake from hydra-head
|
2
|
+
|
3
|
+
namespace :jetty do
|
4
|
+
desc "Apply all configs to Testing Server (relies on hydra:jetty:config tasks unless you override it)"
|
5
|
+
task :config do
|
6
|
+
Rake::Task["hydra:jetty:config"].invoke
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
namespace :hydra do
|
11
|
+
namespace :jetty do
|
12
|
+
desc "Copies the default Solr & Fedora configs into the bundled Hydra Testing Server"
|
13
|
+
task :config do
|
14
|
+
Rake::Task["hydra:jetty:config_fedora"].invoke
|
15
|
+
Rake::Task["hydra:jetty:config_solr"].invoke
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "Copies the contents of solr_conf into the Solr development-core and test-core of Testing Server"
|
19
|
+
task :config_solr do
|
20
|
+
FileList['solr_conf/conf/*'].each do |f|
|
21
|
+
cp("#{f}", 'jetty/solr/development-core/conf/', :verbose => true)
|
22
|
+
cp("#{f}", 'jetty/solr/test-core/conf/', :verbose => true)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "Copies a custom fedora config for the bundled Hydra Testing Server"
|
27
|
+
task :config_fedora do
|
28
|
+
# load a custom fedora.fcfg -
|
29
|
+
if defined?(Rails.root)
|
30
|
+
app_root = Rails.root
|
31
|
+
else
|
32
|
+
app_root = File.join(File.dirname(__FILE__),"..")
|
33
|
+
end
|
34
|
+
|
35
|
+
fcfg = File.join(app_root,"fedora_conf","conf","development","fedora.fcfg")
|
36
|
+
if File.exists?(fcfg)
|
37
|
+
puts "copying over development/fedora.fcfg"
|
38
|
+
cp("#{fcfg}", 'jetty/fedora/default/server/config/', :verbose => true)
|
39
|
+
else
|
40
|
+
puts "#{fcfg} file not found -- skipping fedora config"
|
41
|
+
end
|
42
|
+
fcfg = File.join(app_root,"fedora_conf","conf","test","fedora.fcfg")
|
43
|
+
if File.exists?(fcfg)
|
44
|
+
puts "copying over test/fedora.fcfg"
|
45
|
+
cp("#{fcfg}", 'jetty/fedora/test/server/config/', :verbose => true)
|
46
|
+
else
|
47
|
+
puts "#{fcfg} file not found -- skipping fedora config"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
desc "Copies the default SOLR config files and starts up the fedora instance."
|
52
|
+
task :load => [:config, 'jetty:start']
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" ?>
|
2
|
+
<!--
|
3
|
+
IMPORTANT
|
4
|
+
This copy of the solr schema is only used in the context of testing hydra-head. If you want to make changes available to individual hydra heads, you must apply them to the template in lib/generators/hydra/templates/solr_config
|
5
|
+
-->
|
6
|
+
<schema name="Hydra" version="1.1">
|
7
|
+
<!-- For complete comments from the Solr project example schema.xml:
|
8
|
+
http://svn.apache.org/viewvc/lucene/dev/trunk/solr/example/solr/conf/schema.xml?view=markup
|
9
|
+
See also:
|
10
|
+
http://wiki.apache.org/solr/SchemaXml
|
11
|
+
-->
|
12
|
+
<types>
|
13
|
+
<fieldType name="string" class="solr.StrField" sortMissingLast="true" omitNorms="true"/>
|
14
|
+
<fieldType name="boolean" class="solr.BoolField" sortMissingLast="true" omitNorms="true"/>
|
15
|
+
<fieldType name="integer" class="solr.IntField" omitNorms="true"/>
|
16
|
+
<fieldType name="long" class="solr.LongField" omitNorms="true"/>
|
17
|
+
<fieldType name="float" class="solr.FloatField" omitNorms="true"/>
|
18
|
+
<fieldType name="double" class="solr.DoubleField" omitNorms="true"/>
|
19
|
+
<fieldType name="sint" class="solr.SortableIntField" sortMissingLast="true" omitNorms="true"/>
|
20
|
+
<fieldType name="slong" class="solr.SortableLongField" sortMissingLast="true" omitNorms="true"/>
|
21
|
+
<fieldType name="sfloat" class="solr.SortableFloatField" sortMissingLast="true" omitNorms="true"/>
|
22
|
+
<fieldType name="sdouble" class="solr.SortableDoubleField" sortMissingLast="true" omitNorms="true"/>
|
23
|
+
<fieldType name="date" class="solr.DateField" sortMissingLast="true" omitNorms="true"/>
|
24
|
+
<fieldType name="random" class="solr.RandomSortField" indexed="true" />
|
25
|
+
|
26
|
+
<fieldType name="text_ws" class="solr.TextField" positionIncrementGap="100">
|
27
|
+
<analyzer>
|
28
|
+
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
|
29
|
+
</analyzer>
|
30
|
+
</fieldType>
|
31
|
+
|
32
|
+
<fieldType name="text" class="solr.TextField" positionIncrementGap="100">
|
33
|
+
<analyzer type="index">
|
34
|
+
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
|
35
|
+
<filter class="solr.StopFilterFactory"
|
36
|
+
ignoreCase="true"
|
37
|
+
words="stopwords.txt"
|
38
|
+
enablePositionIncrements="true"
|
39
|
+
/>
|
40
|
+
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="1" catenateNumbers="1" catenateAll="0" splitOnCaseChange="1"/>
|
41
|
+
<filter class="solr.LowerCaseFilterFactory"/>
|
42
|
+
<filter class="solr.EnglishPorterFilterFactory" protected="protwords.txt"/>
|
43
|
+
<filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
|
44
|
+
</analyzer>
|
45
|
+
<analyzer type="query">
|
46
|
+
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
|
47
|
+
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
|
48
|
+
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
|
49
|
+
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
|
50
|
+
<filter class="solr.LowerCaseFilterFactory"/>
|
51
|
+
<filter class="solr.EnglishPorterFilterFactory" protected="protwords.txt"/>
|
52
|
+
<filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
|
53
|
+
</analyzer>
|
54
|
+
</fieldType>
|
55
|
+
|
56
|
+
<fieldType name="textTight" class="solr.TextField" positionIncrementGap="100" >
|
57
|
+
<analyzer>
|
58
|
+
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
|
59
|
+
<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="false"/>
|
60
|
+
<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
|
61
|
+
<filter class="solr.WordDelimiterFilterFactory" generateWordParts="0" generateNumberParts="0" catenateWords="1" catenateNumbers="1" catenateAll="0"/>
|
62
|
+
<filter class="solr.LowerCaseFilterFactory"/>
|
63
|
+
<filter class="solr.EnglishPorterFilterFactory" protected="protwords.txt"/>
|
64
|
+
<filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
|
65
|
+
</analyzer>
|
66
|
+
</fieldType>
|
67
|
+
|
68
|
+
<fieldType name="alphaOnlySort" class="solr.TextField" sortMissingLast="true" omitNorms="true">
|
69
|
+
<analyzer>
|
70
|
+
<tokenizer class="solr.KeywordTokenizerFactory"/>
|
71
|
+
<filter class="solr.LowerCaseFilterFactory" />
|
72
|
+
<filter class="solr.TrimFilterFactory" />
|
73
|
+
<filter class="solr.PatternReplaceFilterFactory"
|
74
|
+
pattern="([^a-z])" replacement="" replace="all"
|
75
|
+
/>
|
76
|
+
</analyzer>
|
77
|
+
</fieldType>
|
78
|
+
|
79
|
+
<fieldtype name="ignored" stored="false" indexed="false" class="solr.StrField" />
|
80
|
+
|
81
|
+
</types>
|
82
|
+
|
83
|
+
<!-- For complete comments from the Solr project example schema.xml:
|
84
|
+
http://svn.apache.org/viewvc/lucene/dev/trunk/solr/example/solr/conf/schema.xml?view=markup
|
85
|
+
See also:
|
86
|
+
http://wiki.apache.org/solr/SchemaXml
|
87
|
+
-->
|
88
|
+
<fields>
|
89
|
+
|
90
|
+
<field name="id" type="string" indexed="true" stored="true" required="true" />
|
91
|
+
<field name="text" type="text" indexed="true" stored="true" multiValued="true"/>
|
92
|
+
<field name="timestamp" type="date" indexed="true" stored="true" default="NOW" multiValued="false"/>
|
93
|
+
|
94
|
+
<!-- format is used for facet, display, and choosing which partial to use for the show view, so it must be stored and indexed -->
|
95
|
+
<field name="format" type="string" indexed="true" stored="true"/>
|
96
|
+
<!-- pub_date is assumed by Blacklight's default configuration, so we must define it here to avoid errors -->
|
97
|
+
<field name="pub_date" type="string" indexed="true" stored="true" multiValued="true"/>
|
98
|
+
|
99
|
+
<dynamicField name="*_i" type="sint" indexed="true" stored="true"/>
|
100
|
+
<dynamicField name="*_s" type="string" indexed="true" stored="true" multiValued="true"/>
|
101
|
+
<dynamicField name="*_l" type="slong" indexed="true" stored="true"/>
|
102
|
+
<dynamicField name="*_t" type="text" indexed="true" stored="true" multiValued="true"/>
|
103
|
+
<dynamicField name="*_b" type="boolean" indexed="true" stored="true"/>
|
104
|
+
<dynamicField name="*_f" type="sfloat" indexed="true" stored="true"/>
|
105
|
+
<dynamicField name="*_d" type="sdouble" indexed="true" stored="true"/>
|
106
|
+
<dynamicField name="*_dt" type="date" indexed="true" stored="true"/>
|
107
|
+
|
108
|
+
<dynamicField name="random*" type="random" />
|
109
|
+
<!-- FIXME: Solr can't sort on a multivalued field -->
|
110
|
+
<dynamicField name="*_sort" type="string" indexed="true" stored="false" multiValued="true"/>
|
111
|
+
<!-- FIXME: generally, facet fields are not stored -->
|
112
|
+
<dynamicField name="*_facet" type="string" indexed="true" stored="true" multiValued="true" />
|
113
|
+
<dynamicField name="*_display" type="string" indexed="false" stored="true" multiValued="true" />
|
114
|
+
|
115
|
+
</fields>
|
116
|
+
|
117
|
+
<uniqueKey>id</uniqueKey>
|
118
|
+
<defaultSearchField>text</defaultSearchField>
|
119
|
+
<solrQueryParser defaultOperator="AND" />
|
120
|
+
<copyField source="*_facet" dest="text" />
|
121
|
+
<copyField source="*_t" dest="text" />
|
122
|
+
<copyField source="*_s" dest="text" />
|
123
|
+
|
124
|
+
</schema>
|