Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Projection scenarios #70

Merged
merged 20 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions apache/conf/test-site.conf
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@

# LogLevel debug

RewriteEngine on
RewriteRule "^/projection/private/but-not-really/" "/projection/public/index.html" [PT]
RewriteRule "^/projection/public/but-not-really/" "/projection/private/index.html" [PT]
RewriteRule "^/projection/private-also/but-not-really/" "/projection/private/index.html" [PT]

<Location /hosted>
ScriptAlias /lauth/test-site/cgi/printenv
AuthType RemoteUser
Expand All @@ -26,6 +31,22 @@
Require all granted
</Directory>

<Location "/projection/private/">
AuthType RemoteUser
<RequireAll>
Require valid-user
Require lauth
</RequireAll>
</Location>

<Location "/projection/private-also/">
AuthType RemoteUser
<RequireAll>
Require valid-user
Require lauth
</RequireAll>
</Location>

<Location "/restricted-by-username/">
AuthType Basic
AuthName "Restricted Resource"
Expand Down
102 changes: 102 additions & 0 deletions db/projection.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
INSERT INTO aa_coll VALUES(
'projection-public', -- uniqueIdentifier
'projection-public', -- commonName
'auth system testing: projection',
'unused', -- dlpsClass
'none', -- dlpsSource (unused)
'pw', -- dlpsAuthenMethod
'n', -- dlpsAuthzType
't', -- dlpsPartlyPublic
0, -- manager
CURRENT_TIMESTAMP, 'root', -- modified info
'f' -- deleted
);

INSERT INTO aa_coll_obj VALUES(
'www.lauth.local', -- server hostname, not vhost
'/lauth/test-site/web/projection/public%', -- dlpsPath
'projection-public', -- coll.uniqueIdentifier
CURRENT_TIMESTAMP, 'root', -- modified info
'f' -- deleted
);

INSERT INTO aa_coll VALUES(
'projection-private', -- uniqueIdentifier
'projection-private', -- commonName
'auth system testing: projection',
'unused', -- dlpsClass
'none', -- dlpsSource (unused)
'pw', -- dlpsAuthenMethod
'n', -- dlpsAuthzType
'f', -- dlpsPartlyPublic
0, -- manager
CURRENT_TIMESTAMP, 'root', -- modified info
'f' -- deleted
);

INSERT INTO aa_coll_obj VALUES(
'www.lauth.local', -- server hostname, not vhost
'/lauth/test-site/web/projection/private%', -- dlpsPath
'projection-private', -- coll.uniqueIdentifier
CURRENT_TIMESTAMP, 'root', -- modified info
'f' -- deleted
);

INSERT INTO aa_may_access VALUES(
NULL, -- uniqueIdentifier
'lauth-allowed', -- userid
NULL, -- user_grp
NULL, -- inst
'projection-private', -- coll
CURRENT_TIMESTAMP,
'root',
NULL,
'f'
);

INSERT INTO aa_coll VALUES(
'projection-private-also', -- uniqueIdentifier
'projection-private-also', -- commonName
'auth system testing: projection',
'unused', -- dlpsClass
'none', -- dlpsSource (unused)
'pw', -- dlpsAuthenMethod
'n', -- dlpsAuthzType
'f', -- dlpsPartlyPublic
0, -- manager
CURRENT_TIMESTAMP, 'root', -- modified info
'f' -- deleted
);

INSERT INTO aa_coll_obj VALUES(
'www.lauth.local', -- server hostname, not vhost
'/lauth/test-site/web/projection/private-also%', -- dlpsPath
'projection-private-also', -- coll.uniqueIdentifier
CURRENT_TIMESTAMP, 'root', -- modified info
'f' -- deleted
);

INSERT INTO aa_user VALUES(
'lauth-allowed-also',NULL,'Lauth',NULL,'Tester-Allowed','[email protected]',
NULL, -- org unit
'Library auth system test user - this user is granted access',
'Ann Arbor','MI','48109-119',NULL,NULL,'Staff',NULL,
'!none', -- umich id, !none
'@umich.edu', -- password, @umich.edu MAY signify SSO
0,NULL,
CURRENT_TIMESTAMP,'root', -- modified
NULL, -- expiry
'f'
);

INSERT INTO aa_may_access VALUES(
NULL, -- uniqueIdentifier
'lauth-allowed-also', -- userid
NULL, -- user_grp
NULL, -- inst
'projection-private-also', -- coll
CURRENT_TIMESTAMP,
'root',
NULL,
'f'
)
1 change: 1 addition & 0 deletions db/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,5 @@ if [[ $all == "true" ]]; then
mariadb --user=$user --host=$host --port=$port --password=$password $database < "$directory/test-fixture.sql"
mariadb --user=$user --host=$host --port=$port --password=$password $database < "$directory/network.sql"
mariadb --user=$user --host=$host --port=$port --password=$password $database < "$directory/delegation.sql"
mariadb --user=$user --host=$host --port=$port --password=$password $database < "$directory/projection.sql"
fi
6 changes: 3 additions & 3 deletions lauth/app/ops/authorize.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def call
collection = collection_repo.find_by_uri(request.uri)
case collection.dlpsAuthzType
when "n"
normal_mode
normal_mode(collection: collection)
when "d"
delegated_mode(collection: collection)
else
Expand Down Expand Up @@ -52,10 +52,10 @@ def delegated_mode(collection:)
)
end

def normal_mode
def normal_mode(collection:)
relevant_grants = grant_repo.for(
username: request.user,
uri: request.uri,
collection: collection,
client_ip: request.client_ip
)
determination = if relevant_grants.any?
Expand Down
18 changes: 18 additions & 0 deletions lauth/app/repositories/collection_repo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,30 @@ class CollectionRepo < ROM::Repository[:collections]
include Deps[container: "persistence.rom"]
struct_namespace Lauth

# Find a collection via its uri location, specifically the dlpsPath.
# This prefers the "most specific" location. We define that here as
# the path with the deepest nesting, and in case of a tie we then prefer
# whichever path is longest. I.e. the path /foo/bar/baz% is more specific
# than /foo/bar/b%.
# There is an assumption that all dlpsPath values end in the SQL wildcard %.
# @param uri [String]
# @return [Collection]
def find_by_uri(uri)
dataset = collections
.dataset
.where(collections[:dlpsDeleted].is("f"))
.join(locations.name.dataset, coll: :uniqueIdentifier, dlpsDeleted: "f")
.where(Sequel.ilike(uri, locations[:dlpsPath]))
.select_append(Sequel.as( # count the slashes
Sequel.expr {
char_length(:dlpsPath) - char_length(replace(:dlpsPath, "/", ""))
},
:path_depth
))
.order(
Sequel.desc(:path_depth),
Sequel.desc(Sequel.expr { length(:dlpsPath) })
)
collections.class.new(dataset).to_a.first
end

Expand Down
82 changes: 27 additions & 55 deletions lauth/app/repositories/grant_repo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,62 +10,50 @@ def find(id)

def for_collection_class(username:, client_ip:, collection_class:)
smallest_network = smallest_network_for_ip(client_ip)

ds = grants
.dataset
.where(grants[:dlpsDeleted].is("f"))
.join(collections.name.dataset, uniqueIdentifier: :coll, dlpsDeleted: "f")
.left_join(users.name.dataset, userid: grants[:userid], dlpsDeleted: "f")
.left_join(institutions.name.dataset, uniqueIdentifier: grants[:inst], dlpsDeleted: "f")
.left_join(institution_memberships.name.dataset, inst: grants[:inst], dlpsDeleted: "f")
.left_join(groups.name.dataset, uniqueIdentifier: grants[:user_grp], dlpsDeleted: "f")
.left_join(group_memberships.name.dataset, user_grp: grants[:user_grp], dlpsDeleted: "f")
.left_join(Sequel.as(smallest_network, :smallest), inst: grants[:inst])
ds = base_grants_for(username: username, network: smallest_network)
.join(collections.name.dataset, uniqueIdentifier: grants[:coll], dlpsDeleted: "f")
.where(collections[:dlpsClass] => collection_class)
.where(
Sequel.|(
Sequel.&(
Sequel.~(users[:userid] => nil),
{users[:userid] => username}
),
Sequel.&(
Sequel.~(institutions[:uniqueIdentifier] => nil),
Sequel.~(institution_memberships[:userid] => nil),
{institution_memberships[:userid] => username}
),
Sequel.&(
Sequel.~(groups[:uniqueIdentifier] => nil),
Sequel.~(group_memberships[:userid] => nil),
{group_memberships[:userid] => username}
),
Sequel.&(
Sequel.~(Sequel[:smallest][:inst] => nil),
{Sequel[:smallest][:dlpsAccessSwitch] => "allow"}
)
)
)

rel = grants.class.new(ds)
rel.to_a
end

def for(username:, uri:, client_ip: nil)
def for(username:, collection:, client_ip: nil)
return [] unless collection&.dlpsDeleted == "f"

smallest_network = smallest_network_for_ip(client_ip)
ds = base_grants_for(username: username, network: smallest_network)
.where(grants[:coll] => collection.uniqueIdentifier)

rel = grants.class.new(ds)
rel.combine(:user, institutions: {institution_memberships: :users}).to_a
end

private

ds = grants
def smallest_network_for_ip(client_ip)
ip = client_ip ? IPAddr.new(client_ip).to_i : nil
networks
.dataset
.where(dlpsDeleted: "f")
.where { dlpsAddressStart <= ip }
.where { dlpsAddressEnd >= ip }
.select_append(Sequel.as(Sequel.expr { dlpsAddressEnd - dlpsAddressStart }, :block_size))
.order(Sequel.asc(:block_size)).limit(1)
end

def base_grants_for(username:, network:)
grants
.dataset
.where(grants[:dlpsDeleted].is("f"))
.join(collections.name.dataset, uniqueIdentifier: :coll, dlpsDeleted: "f")
.join(locations.name.dataset, coll: :uniqueIdentifier, dlpsDeleted: "f")
.left_join(users.name.dataset, userid: grants[:userid], dlpsDeleted: "f")
.left_join(institutions.name.dataset, uniqueIdentifier: grants[:inst], dlpsDeleted: "f")
.left_join(institution_memberships.name.dataset, inst: :uniqueIdentifier, dlpsDeleted: "f")
.left_join(Sequel.as(users.name.dataset, :inst_users), userid: :userid, dlpsDeleted: "f")
.left_join(groups.name.dataset, uniqueIdentifier: grants[:user_grp], dlpsDeleted: "f")
.left_join(group_memberships.name.dataset, user_grp: :uniqueIdentifier, dlpsDeleted: "f")
.left_join(Sequel.as(users.name.dataset, :group_users), userid: :userid, dlpsDeleted: "f")
.left_join(Sequel.as(smallest_network, :smallest), inst: institutions[:uniqueIdentifier])
.where(Sequel.ilike(uri, locations[:dlpsPath]))
.left_join(Sequel.as(network, :smallest), inst: institutions[:uniqueIdentifier])
.where(
Sequel.|(
Sequel.&(
Expand All @@ -88,22 +76,6 @@ def for(username:, uri:, client_ip: nil)
)
)
)

rel = grants.class.new(ds)
rel.combine(:user, collections: :locations, institutions: {institution_memberships: :users}).to_a
end

private

def smallest_network_for_ip(client_ip)
ip = client_ip ? IPAddr.new(client_ip).to_i : nil
networks
.dataset
.where(dlpsDeleted: "f")
.where { dlpsAddressStart <= ip }
.where { dlpsAddressEnd >= ip }
.select_append(Sequel.as(Sequel.expr { dlpsAddressEnd - dlpsAddressStart }, :block_size))
.order(Sequel.asc(:block_size)).limit(1)
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions lauth/spec/ops/authorize_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
it "allows a request with a grant" do
allow(grant_repo).to receive(:for).with(
username: "cool_dude",
uri: "/some/uri/",
collection: anything,
client_ip: "10.11.22.33"
).and_return([:somegrant])

Expand All @@ -36,7 +36,7 @@
it "denies a request without any grants" do
allow(grant_repo).to receive(:for).with(
username: "cool_dude",
uri: "/some/uri/",
collection: anything,
client_ip: "10.11.22.33"
).and_return([])

Expand Down
11 changes: 7 additions & 4 deletions lauth/spec/repositories/collection_repo_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
subject(:repo) { Lauth::Repositories::CollectionRepo.new }

describe "#find_by_uri" do
it "finds the collection for the given location path" do
collection = Lauth::Fab::Collection.create
Factory[:location, dlpsPath: "/cool/path%", collection: collection]
it "finds the most specific collection for the given location path" do
expected_collection = Lauth::Fab::Collection.create(uniqueIdentifier: "expected-collection")
wrong_collection = Lauth::Fab::Collection.create
Factory[:location, dlpsPath: "/cool/p%", collection: wrong_collection]
Factory[:location, dlpsPath: "/uncool%", collection: wrong_collection]
Factory[:location, dlpsPath: "/cool/path%", collection: expected_collection]

found = repo.find_by_uri("/cool/path")

expect(found.uniqueIdentifier).to eq collection.uniqueIdentifier
expect(found.uniqueIdentifier).to eq "expected-collection"
end
end

Expand Down
Loading