hydra-access-controls 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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>
|