263 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			263 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Python
		
	
	
	
| import pytest
 | |
| 
 | |
| from funkwhale_api.users.oauth import permissions, scopes
 | |
| 
 | |
| 
 | |
| @pytest.mark.parametrize(
 | |
|     "required_scope, request_scopes, expected",
 | |
|     [
 | |
|         (None, {}, True),
 | |
|         ("write:profile", {"write"}, True),
 | |
|         ("write:profile", {"read"}, False),
 | |
|         ("write:profile", {"read:profile"}, False),
 | |
|         ("write:profile", {"write:profile"}, True),
 | |
|         ("read:profile", {"read"}, True),
 | |
|         ("read:profile", {"write"}, False),
 | |
|         ("read:profile", {"read:profile"}, True),
 | |
|         ("read:profile", {"write:profile"}, False),
 | |
|         ("write:profile", {"write"}, True),
 | |
|         ("write:profile", {"read:profile"}, False),
 | |
|         ("write:profile", {"write:profile"}, True),
 | |
|         ("write:profile", {"write"}, True),
 | |
|         ("write:profile", {"read:profile"}, False),
 | |
|         ("write:profile", {"write:profile"}, True),
 | |
|         ("write:profile", {"write"}, True),
 | |
|         ("write:profile", {"read:profile"}, False),
 | |
|         ("write:profile", {"write:profile"}, True),
 | |
|     ],
 | |
| )
 | |
| def test_should_allow(required_scope, request_scopes, expected):
 | |
|     assert (
 | |
|         permissions.should_allow(
 | |
|             required_scope=required_scope, request_scopes=request_scopes
 | |
|         )
 | |
|         is expected
 | |
|     )
 | |
| 
 | |
| 
 | |
| @pytest.mark.parametrize("method", ["OPTIONS", "HEAD"])
 | |
| def test_scope_permission_safe_methods(method, mocker, factories):
 | |
|     view = mocker.Mock(required_scope="write:profile", anonymous_policy=False)
 | |
|     request = mocker.Mock(method=method)
 | |
| 
 | |
|     p = permissions.ScopePermission()
 | |
| 
 | |
|     assert p.has_permission(request, view) is True
 | |
| 
 | |
| 
 | |
| @pytest.mark.parametrize(
 | |
|     "policy, preference, expected",
 | |
|     [
 | |
|         (True, False, True),
 | |
|         (False, False, False),
 | |
|         ("setting", True, False),
 | |
|         ("setting", False, True),
 | |
|     ],
 | |
| )
 | |
| def test_scope_permission_anonymous_policy(
 | |
|     policy, preference, expected, preferences, mocker, anonymous_user
 | |
| ):
 | |
|     preferences["common__api_authentication_required"] = preference
 | |
|     view = mocker.Mock(
 | |
|         required_scope="libraries", anonymous_policy=policy, anonymous_scopes=set()
 | |
|     )
 | |
|     request = mocker.Mock(method="GET", user=anonymous_user, actor=None, scopes=None)
 | |
| 
 | |
|     p = permissions.ScopePermission()
 | |
| 
 | |
|     assert p.has_permission(request, view) is expected
 | |
| 
 | |
| 
 | |
| def test_scope_permission_dict_no_required(mocker, anonymous_user):
 | |
|     view = mocker.Mock(
 | |
|         required_scope={"read": None, "write": "write:profile"},
 | |
|         anonymous_policy=True,
 | |
|         action="read",
 | |
|         anonymous_scopes=set(),
 | |
|     )
 | |
|     request = mocker.Mock(method="GET", user=anonymous_user, actor=None, scopes=None)
 | |
| 
 | |
|     p = permissions.ScopePermission()
 | |
| 
 | |
|     assert p.has_permission(request, view) is True
 | |
| 
 | |
| 
 | |
| @pytest.mark.parametrize(
 | |
|     "required_scope, method, action, expected_scope",
 | |
|     [
 | |
|         ("profile", "GET", "read", "read:profile"),
 | |
|         ("profile", "POST", "write", "write:profile"),
 | |
|         ({"read": "read:profile"}, "GET", "read", "read:profile"),
 | |
|         ({"write": "write:profile"}, "POST", "write", "write:profile"),
 | |
|     ],
 | |
| )
 | |
| def test_scope_permission_user(
 | |
|     required_scope, method, action, expected_scope, mocker, factories
 | |
| ):
 | |
|     user = factories["users.User"]()
 | |
|     should_allow = mocker.patch.object(permissions, "should_allow")
 | |
|     request = mocker.Mock(method=method, user=user, actor=None, scopes=None)
 | |
|     view = mocker.Mock(
 | |
|         required_scope=required_scope, anonymous_policy=False, action=action
 | |
|     )
 | |
| 
 | |
|     p = permissions.ScopePermission()
 | |
| 
 | |
|     assert p.has_permission(request, view) == should_allow.return_value
 | |
| 
 | |
|     should_allow.assert_called_once_with(
 | |
|         required_scope=expected_scope,
 | |
|         request_scopes=scopes.get_from_permissions(**user.get_permissions()),
 | |
|     )
 | |
| 
 | |
| 
 | |
| def test_scope_permission_token(mocker, factories):
 | |
|     token = factories["users.AccessToken"](
 | |
|         scope="write:profile read:playlists",
 | |
|         application__scope="write:profile read:playlists",
 | |
|     )
 | |
|     should_allow = mocker.patch.object(permissions, "should_allow")
 | |
|     request = mocker.Mock(method="POST", auth=token)
 | |
|     view = mocker.Mock(required_scope="profile", anonymous_policy=False)
 | |
| 
 | |
|     p = permissions.ScopePermission()
 | |
| 
 | |
|     assert p.has_permission(request, view) == should_allow.return_value
 | |
| 
 | |
|     should_allow.assert_called_once_with(
 | |
|         required_scope="write:profile",
 | |
|         request_scopes={"write:profile", "read:playlists"},
 | |
|     )
 | |
| 
 | |
| 
 | |
| def test_scope_permission_request_scopes(mocker, factories):
 | |
|     should_allow = mocker.patch.object(permissions, "should_allow")
 | |
|     request = mocker.Mock(method="POST", scopes=["write:profile", "read:playlists"])
 | |
|     view = mocker.Mock(required_scope="profile", anonymous_policy=False)
 | |
|     p = permissions.ScopePermission()
 | |
| 
 | |
|     assert p.has_permission(request, view) == should_allow.return_value
 | |
| 
 | |
|     should_allow.assert_called_once_with(
 | |
|         required_scope="write:profile",
 | |
|         request_scopes={"write:profile", "read:playlists"},
 | |
|     )
 | |
| 
 | |
| 
 | |
| def test_scope_permission_actor(mocker, factories, anonymous_user):
 | |
|     should_allow = mocker.patch.object(permissions, "should_allow")
 | |
|     request = mocker.Mock(
 | |
|         method="POST",
 | |
|         actor=factories["federation.Actor"](),
 | |
|         user=anonymous_user,
 | |
|         scopes=None,
 | |
|     )
 | |
|     view = mocker.Mock(required_scope="profile", anonymous_policy=False)
 | |
|     p = permissions.ScopePermission()
 | |
| 
 | |
|     assert p.has_permission(request, view) == should_allow.return_value
 | |
| 
 | |
|     should_allow.assert_called_once_with(
 | |
|         required_scope="write:profile", request_scopes=scopes.FEDERATION_REQUEST_SCOPES
 | |
|     )
 | |
| 
 | |
| 
 | |
| def test_scope_permission_token_anonymous_user_auth_required(
 | |
|     mocker, factories, anonymous_user, preferences
 | |
| ):
 | |
|     preferences["common__api_authentication_required"] = True
 | |
|     should_allow = mocker.patch.object(permissions, "should_allow")
 | |
|     request = mocker.Mock(method="POST", user=anonymous_user, actor=None, scopes=None)
 | |
|     view = mocker.Mock(required_scope="profile", anonymous_policy=False)
 | |
| 
 | |
|     p = permissions.ScopePermission()
 | |
| 
 | |
|     assert p.has_permission(request, view) is False
 | |
| 
 | |
|     should_allow.assert_not_called()
 | |
| 
 | |
| 
 | |
| def test_scope_permission_token_anonymous_user_auth_not_required(
 | |
|     mocker, factories, anonymous_user, preferences
 | |
| ):
 | |
|     preferences["common__api_authentication_required"] = False
 | |
|     should_allow = mocker.patch.object(permissions, "should_allow")
 | |
|     request = mocker.Mock(method="POST", user=anonymous_user, actor=None, scopes=None)
 | |
|     view = mocker.Mock(
 | |
|         required_scope="profile", anonymous_policy="setting", anonymous_scopes=set()
 | |
|     )
 | |
| 
 | |
|     p = permissions.ScopePermission()
 | |
| 
 | |
|     assert p.has_permission(request, view) == should_allow.return_value
 | |
| 
 | |
|     should_allow.assert_called_once_with(
 | |
|         required_scope="write:profile", request_scopes=scopes.ANONYMOUS_SCOPES
 | |
|     )
 | |
| 
 | |
| 
 | |
| def test_scope_permission_token_expired(mocker, factories, now):
 | |
|     token = factories["users.AccessToken"](
 | |
|         scope="profile:write playlists:read", expires=now
 | |
|     )
 | |
|     should_allow = mocker.patch.object(permissions, "should_allow")
 | |
|     request = mocker.Mock(method="POST", auth=token)
 | |
|     view = mocker.Mock(required_scope="profile", anonymous_policy=False)
 | |
| 
 | |
|     p = permissions.ScopePermission()
 | |
| 
 | |
|     assert p.has_permission(request, view) is False
 | |
| 
 | |
|     should_allow.assert_not_called()
 | |
| 
 | |
| 
 | |
| def test_scope_permission_token_no_user(mocker, factories, now):
 | |
|     token = factories["users.AccessToken"](
 | |
|         scope="profile:write playlists:read", user=None
 | |
|     )
 | |
|     should_allow = mocker.patch.object(permissions, "should_allow")
 | |
|     request = mocker.Mock(method="POST", auth=token)
 | |
|     view = mocker.Mock(required_scope="profile", anonymous_policy=False)
 | |
| 
 | |
|     p = permissions.ScopePermission()
 | |
| 
 | |
|     assert p.has_permission(request, view) is False
 | |
| 
 | |
|     should_allow.assert_not_called()
 | |
| 
 | |
| 
 | |
| def test_scope_permission_token_honor_app_scopes(mocker, factories, now):
 | |
|     # token contains read access, but app scope only allows profile:write
 | |
|     token = factories["users.AccessToken"](
 | |
|         scope="write:profile read", application__scope="write:profile"
 | |
|     )
 | |
|     should_allow = mocker.patch.object(permissions, "should_allow")
 | |
|     request = mocker.Mock(method="POST", auth=token)
 | |
|     view = mocker.Mock(required_scope="profile", anonymous_policy=False)
 | |
| 
 | |
|     p = permissions.ScopePermission()
 | |
| 
 | |
|     assert p.has_permission(request, view) == should_allow.return_value
 | |
| 
 | |
|     should_allow.assert_called_once_with(
 | |
|         required_scope="write:profile", request_scopes={"write:profile"}
 | |
|     )
 | |
| 
 | |
| 
 | |
| def test_scope_permission_token_honor_allowed_app_scopes(mocker, factories, now):
 | |
|     mocker.patch.object(scopes, "OAUTH_APP_SCOPES", {"read:profile"})
 | |
|     token = factories["users.AccessToken"](
 | |
|         scope="write:profile read:profile read",
 | |
|         application__scope="write:profile read:profile read",
 | |
|     )
 | |
|     should_allow = mocker.patch.object(permissions, "should_allow")
 | |
|     request = mocker.Mock(method="POST", auth=token)
 | |
|     view = mocker.Mock(required_scope="profile", anonymous_policy=False)
 | |
|     p = permissions.ScopePermission()
 | |
| 
 | |
|     assert p.has_permission(request, view) == should_allow.return_value
 | |
| 
 | |
|     should_allow.assert_called_once_with(
 | |
|         required_scope="write:profile", request_scopes={"read:profile"}
 | |
|     )
 |