Skip to content

Commit 2e5e7b5

Browse files
Add authorization decorator for connector services to restrict catalog access (#688)
Implements connector-level authorization based on the userName from the request context. This allows catalogs to restrict access to specific authenticated callers. Key changes: - Add AuthorizingConnectorCatalogService decorator - Add AuthorizingConnectorDatabaseService decorator - Add AuthorizingConnectorTableService decorator - Add AuthorizingConnectorPartitionService decorator - Add ConnectorAuthorizationUtil shared utility class - Add CatalogUnauthorizedException for authorization failures - Update ConnectorFactoryDecorator to chain authorization decorators Configuration: - connector.authorization-required=true - enables authorization - connector.authorized-callers=caller1,caller2 - comma-separated allowed callers Authorization checks the userName from MetacatRequestContext against the allowed callers list. Requests without a userName or with an unauthorized userName receive a CatalogUnauthorizedException.
1 parent 678c7fc commit 2e5e7b5

13 files changed

+2642
-2
lines changed
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
*
3+
* Copyright 2024 Netflix, Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
package com.netflix.metacat.common.server.connectors;
19+
20+
import com.netflix.metacat.common.QualifiedName;
21+
import com.netflix.metacat.common.dto.Pageable;
22+
import com.netflix.metacat.common.dto.Sort;
23+
import com.netflix.metacat.common.server.connectors.model.CatalogInfo;
24+
import com.netflix.metacat.common.server.util.ConnectorAuthorizationUtil;
25+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
26+
import lombok.Getter;
27+
import lombok.NonNull;
28+
import lombok.RequiredArgsConstructor;
29+
30+
import javax.annotation.Nullable;
31+
import java.util.List;
32+
import java.util.Set;
33+
34+
/**
35+
* Connector decorator that authorizes catalog service calls based on SSO caller context.
36+
* Only callers in the allowed list can access this connector's operations.
37+
*
38+
* @author jursetta
39+
*/
40+
@RequiredArgsConstructor
41+
public class AuthorizingConnectorCatalogService implements ConnectorCatalogService {
42+
43+
@Getter
44+
@NonNull
45+
private final ConnectorCatalogService delegate;
46+
47+
@NonNull
48+
private final Set<String> allowedCallers;
49+
50+
@NonNull
51+
private final String catalogName;
52+
53+
@Override
54+
public void create(final ConnectorRequestContext context, final CatalogInfo resource) {
55+
ConnectorAuthorizationUtil.checkAuthorization(catalogName, allowedCallers, "create", resource.getName());
56+
delegate.create(context, resource);
57+
}
58+
59+
@Override
60+
public void update(final ConnectorRequestContext context, final CatalogInfo resource) {
61+
ConnectorAuthorizationUtil.checkAuthorization(catalogName, allowedCallers, "update", resource.getName());
62+
delegate.update(context, resource);
63+
}
64+
65+
@Override
66+
public void delete(final ConnectorRequestContext context, final QualifiedName name) {
67+
ConnectorAuthorizationUtil.checkAuthorization(catalogName, allowedCallers, "delete", name);
68+
delegate.delete(context, name);
69+
}
70+
71+
@Override
72+
public CatalogInfo get(final ConnectorRequestContext context, final QualifiedName name) {
73+
ConnectorAuthorizationUtil.checkAuthorization(catalogName, allowedCallers, "get", name);
74+
return delegate.get(context, name);
75+
}
76+
77+
@Override
78+
@SuppressFBWarnings
79+
public boolean exists(final ConnectorRequestContext context, final QualifiedName name) {
80+
ConnectorAuthorizationUtil.checkAuthorization(catalogName, allowedCallers, "exists", name);
81+
return delegate.exists(context, name);
82+
}
83+
84+
@Override
85+
public List<CatalogInfo> list(
86+
final ConnectorRequestContext context,
87+
final QualifiedName name,
88+
@Nullable final QualifiedName prefix,
89+
@Nullable final Sort sort,
90+
@Nullable final Pageable pageable
91+
) {
92+
ConnectorAuthorizationUtil.checkAuthorization(catalogName, allowedCallers, "list", name);
93+
return delegate.list(context, name, prefix, sort, pageable);
94+
}
95+
96+
@Override
97+
public List<QualifiedName> listNames(
98+
final ConnectorRequestContext context,
99+
final QualifiedName name,
100+
@Nullable final QualifiedName prefix,
101+
@Nullable final Sort sort,
102+
@Nullable final Pageable pageable
103+
) {
104+
ConnectorAuthorizationUtil.checkAuthorization(catalogName, allowedCallers, "listNames", name);
105+
return delegate.listNames(context, name, prefix, sort, pageable);
106+
}
107+
108+
@Override
109+
public void rename(
110+
final ConnectorRequestContext context,
111+
final QualifiedName oldName,
112+
final QualifiedName newName
113+
) {
114+
ConnectorAuthorizationUtil.checkAuthorization(catalogName, allowedCallers, "rename", oldName);
115+
delegate.rename(context, oldName, newName);
116+
}
117+
118+
/**
119+
* Delegates equals to the underlying service so that decorated services
120+
* representing the same catalog are considered equal. This allows
121+
* ConnectorManager to deduplicate services in its sets.
122+
*/
123+
@Override
124+
public boolean equals(final Object o) {
125+
return delegate.equals(o);
126+
}
127+
128+
/**
129+
* Delegates hashCode to the underlying service for consistency with equals.
130+
*/
131+
@Override
132+
public int hashCode() {
133+
return delegate.hashCode();
134+
}
135+
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/*
2+
*
3+
* Copyright 2024 Netflix, Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
package com.netflix.metacat.common.server.connectors;
19+
20+
import com.netflix.metacat.common.QualifiedName;
21+
import com.netflix.metacat.common.dto.Pageable;
22+
import com.netflix.metacat.common.dto.Sort;
23+
import com.netflix.metacat.common.server.connectors.model.DatabaseInfo;
24+
import com.netflix.metacat.common.server.util.ConnectorAuthorizationUtil;
25+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
26+
import lombok.Getter;
27+
import lombok.NonNull;
28+
import lombok.RequiredArgsConstructor;
29+
30+
import javax.annotation.Nullable;
31+
import java.util.List;
32+
import java.util.Set;
33+
34+
/**
35+
* Connector decorator that authorizes database service calls based on SSO caller context.
36+
* Only callers in the allowed list can access this connector's operations.
37+
*
38+
* @author jursetta
39+
*/
40+
@RequiredArgsConstructor
41+
public class AuthorizingConnectorDatabaseService implements ConnectorDatabaseService {
42+
43+
@Getter
44+
@NonNull
45+
private final ConnectorDatabaseService delegate;
46+
47+
@NonNull
48+
private final Set<String> allowedCallers;
49+
50+
@NonNull
51+
private final String catalogName;
52+
53+
@Override
54+
public void create(final ConnectorRequestContext context, final DatabaseInfo resource) {
55+
ConnectorAuthorizationUtil.checkAuthorization(catalogName, allowedCallers, "create", resource.getName());
56+
delegate.create(context, resource);
57+
}
58+
59+
@Override
60+
public void update(final ConnectorRequestContext context, final DatabaseInfo resource) {
61+
ConnectorAuthorizationUtil.checkAuthorization(catalogName, allowedCallers, "update", resource.getName());
62+
delegate.update(context, resource);
63+
}
64+
65+
@Override
66+
public void delete(final ConnectorRequestContext context, final QualifiedName name) {
67+
ConnectorAuthorizationUtil.checkAuthorization(catalogName, allowedCallers, "delete", name);
68+
delegate.delete(context, name);
69+
}
70+
71+
@Override
72+
public DatabaseInfo get(final ConnectorRequestContext context, final QualifiedName name) {
73+
ConnectorAuthorizationUtil.checkAuthorization(catalogName, allowedCallers, "get", name);
74+
return delegate.get(context, name);
75+
}
76+
77+
@Override
78+
@SuppressFBWarnings
79+
public boolean exists(final ConnectorRequestContext context, final QualifiedName name) {
80+
ConnectorAuthorizationUtil.checkAuthorization(catalogName, allowedCallers, "exists", name);
81+
return delegate.exists(context, name);
82+
}
83+
84+
@Override
85+
public List<DatabaseInfo> list(
86+
final ConnectorRequestContext context,
87+
final QualifiedName name,
88+
@Nullable final QualifiedName prefix,
89+
@Nullable final Sort sort,
90+
@Nullable final Pageable pageable
91+
) {
92+
ConnectorAuthorizationUtil.checkAuthorization(catalogName, allowedCallers, "list", name);
93+
return delegate.list(context, name, prefix, sort, pageable);
94+
}
95+
96+
@Override
97+
public List<QualifiedName> listNames(
98+
final ConnectorRequestContext context,
99+
final QualifiedName name,
100+
@Nullable final QualifiedName prefix,
101+
@Nullable final Sort sort,
102+
@Nullable final Pageable pageable
103+
) {
104+
ConnectorAuthorizationUtil.checkAuthorization(catalogName, allowedCallers, "listNames", name);
105+
return delegate.listNames(context, name, prefix, sort, pageable);
106+
}
107+
108+
@Override
109+
public void rename(
110+
final ConnectorRequestContext context,
111+
final QualifiedName oldName,
112+
final QualifiedName newName
113+
) {
114+
ConnectorAuthorizationUtil.checkAuthorization(catalogName, allowedCallers, "rename", oldName);
115+
delegate.rename(context, oldName, newName);
116+
}
117+
118+
@Override
119+
public List<QualifiedName> listViewNames(
120+
final ConnectorRequestContext context,
121+
final QualifiedName databaseName
122+
) {
123+
ConnectorAuthorizationUtil.checkAuthorization(catalogName, allowedCallers, "listViewNames", databaseName);
124+
return delegate.listViewNames(context, databaseName);
125+
}
126+
127+
/**
128+
* Delegates equals to the underlying service so that decorated services
129+
* representing the same catalog are considered equal. This allows
130+
* ConnectorManager to deduplicate services in its sets.
131+
*/
132+
@Override
133+
public boolean equals(final Object o) {
134+
return delegate.equals(o);
135+
}
136+
137+
/**
138+
* Delegates hashCode to the underlying service for consistency with equals.
139+
*/
140+
@Override
141+
public int hashCode() {
142+
return delegate.hashCode();
143+
}
144+
}

0 commit comments

Comments
 (0)