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

Implement UserMap and GroupMap output. #159

Merged
merged 7 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion cadc-access-control-server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ sourceCompatibility = 1.8

group = 'org.opencadc'

version = '1.3.34'
version = '1.3.33'

description = 'OpenCADC User+Group server library'
def git_url = 'https://github.com/opencadc/ac'
Expand Down
2 changes: 1 addition & 1 deletion cadc-gms/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ sourceCompatibility = 1.8

group = 'org.opencadc'

version = '1.0.9'
version = '1.0.10'

description = 'OpenCADC GMS API library'
def git_url = 'https://github.com/opencadc/ac'
Expand Down
173 changes: 130 additions & 43 deletions cadc-gms/src/main/java/org/opencadc/auth/PosixMapperClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
import ca.nrc.cadc.auth.AuthenticationUtil;
import ca.nrc.cadc.auth.HttpPrincipal;
import ca.nrc.cadc.auth.PosixPrincipal;
import ca.nrc.cadc.cred.client.CredUtil;
import ca.nrc.cadc.io.ResourceIterator;
import ca.nrc.cadc.net.HttpGet;
import ca.nrc.cadc.net.ResourceAlreadyExistsException;
import ca.nrc.cadc.net.ResourceNotFoundException;
Expand All @@ -86,13 +86,9 @@
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URL;
import java.security.AccessControlException;
import java.security.Principal;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.security.auth.Subject;
Expand All @@ -109,14 +105,16 @@ public class PosixMapperClient {

private final String service;
private final Capabilities capabilities;
private final RegistryClient regClient = new RegistryClient();
private final TSVPosixGroupParser tsvPosixGroupParser = new TSVPosixGroupParser();
private final TSVPosixPrincipalParser tsvPosixPrincipalParser = new TSVPosixPrincipalParser();

public PosixMapperClient(URI resourceID) {
if (resourceID == null) {
throw new IllegalArgumentException("resourceID cannot be null");
}
this.service = resourceID.toASCIIString();
try {
final RegistryClient regClient = new RegistryClient();
this.capabilities = regClient.getCapabilities(resourceID);
} catch (ResourceNotFoundException | IOException ex) {
throw new RuntimeException("failed to read capabilities for " + service, ex);
Expand Down Expand Up @@ -186,23 +184,11 @@ public Subject augment(Subject subject)

try (BufferedReader reader = new BufferedReader(new InputStreamReader(get.getInputStream()))) {
String line = reader.readLine();
String[] tokens = line.split("\\s");
if (tokens.length != 3) {
throw new IllegalStateException(
String.format("error parsing query results, expected 3 values, found %s: %s",
tokens.length, line));
}
// format - username uid gid
String username = tokens[0];
int userID = Integer.parseInt(tokens[1]);
int groupID = Integer.parseInt(tokens[2]);
final PosixPrincipal posixPrincipal = tsvPosixPrincipalParser.parse(line);

Set<Principal> principals = new HashSet<>();
PosixPrincipal posixPrincipal = new PosixPrincipal(userID);
posixPrincipal.username = username;
posixPrincipal.defaultGroup = groupID;
principals.add(posixPrincipal);
principals.add(new HttpPrincipal(username));
principals.add(new HttpPrincipal(posixPrincipal.username));
for (Principal p : subject.getPrincipals()) {
if (!(p instanceof HttpPrincipal) && !(p instanceof PosixPrincipal)) {
principals.add(p);
Expand Down Expand Up @@ -233,19 +219,98 @@ public List<PosixGroup> getURI(List<Integer> groups)
return getPosixGroups(null, groups);
}

// use case: skaha needs the complete username-uid map for user containers
// change: adding defaultGroup and username to PosixPrincipal, all fields would be returned here
// note: Iterator allows the client to consume the stream and process it without having to
// store it in memory... scalable but sometimes awkward
public Iterator<PosixPrincipal> getUserMap() {
throw new UnsupportedOperationException();
/**
* Obtain the full mapping of Username to UID mapping to populate the POSIX system mappings. It is assumed
* the underlying API supports writing TSV output.
* <p></p>
* use case: skaha needs the complete username-uid map for user containers
* change: adding defaultGroup and username to PosixPrincipal, all fields would be returned here
* note: Iterator allows the client to consume the stream and process it without having to
* store it in memory... scalable but sometimes awkward
*
* @return Iterator over POSIX principals
* @throws IOException Any I/O related (catch-all) errors.
* @throws ResourceNotFoundException If the UID POSIX Mapping service cannot be found.
* @throws ResourceAlreadyExistsException Not used.
* @throws InterruptedException Should not ever happen.
*/
public ResourceIterator<PosixPrincipal> getUserMap() throws IOException, ResourceNotFoundException,
ResourceAlreadyExistsException, InterruptedException {
final URL userMapURL = getServiceURL(Standards.POSIX_USERMAP);
final HttpGet get = new HttpGet(userMapURL, true);
get.setRequestProperty("accept", "text/tab-separated-values");
get.prepare();

return new ResourceIterator<PosixPrincipal>() {
private final BufferedReader reader = new BufferedReader(new InputStreamReader(get.getInputStream()));
private String line;

@Override
public boolean hasNext() {
try {
return reader.ready() && (line = reader.readLine()) != null;
} catch (IOException ioException) {
throw new RuntimeException(ioException.getMessage(), ioException);
}
}

@Override
public PosixPrincipal next() {
final PosixPrincipal nextPrincipal = tsvPosixPrincipalParser.parse(line);
line = null;
return nextPrincipal;
}

@Override
public void close() throws IOException {
reader.close();
}
};
}

// use case: skaha needs the complete groupname-gid map for user containers
// note: Iterator allows the client to consume the stream and process it without having to
// store it in memory... scalable but sometimes awkward
public Iterator<PosixGroup> getGroupMap() {
throw new UnsupportedOperationException();
/**
* Obtain the full Group Name to GID Mapping. It is assumed the underlying API supports writing TSV output.
* <p></p>
* use case: skaha needs the complete groupname-gid map for user containers
* note: Iterator allows the client to consume the stream and process it without having to
* store it in memory... scalable but sometimes awkward
* @return @return Iterator over POSIX Groups
* @throws IOException Any I/O related (catch-all) errors.
* @throws ResourceNotFoundException If the GID POSIX Mapping service cannot be found.
* @throws ResourceAlreadyExistsException Not used.
* @throws InterruptedException Should not ever happen.
*/
public ResourceIterator<PosixGroup> getGroupMap() throws IOException, ResourceNotFoundException,
ResourceAlreadyExistsException, InterruptedException {
final URL userMapURL = getServiceURL(Standards.POSIX_GROUPMAP);
final HttpGet get = new HttpGet(userMapURL, true);
get.setRequestProperty("accept", "text/tab-separated-values");
get.prepare();

return new ResourceIterator<PosixGroup>() {
private final BufferedReader reader = new BufferedReader(new InputStreamReader(get.getInputStream()));
private String line;
@Override
public boolean hasNext() {
try {
return reader.ready() && (line = reader.readLine()) != null;
} catch (IOException ioException) {
throw new RuntimeException(ioException.getMessage(), ioException);
}
}

@Override
public PosixGroup next() {
final PosixGroup posixGroup = tsvPosixGroupParser.parse(line);
line = null;
return posixGroup;
}

@Override
public void close() throws IOException {
reader.close();
}
};
}

private List<PosixGroup> getPosixGroups(List<GroupURI> groupURIs, List<Integer> groupGIDs)
Expand Down Expand Up @@ -276,23 +341,13 @@ private List<PosixGroup> getPosixGroups(List<GroupURI> groupURIs, List<Integer>
List<PosixGroup> posixGroups = new ArrayList<>();
while (reader.ready()) {
String line = reader.readLine();
log.debug("line: " + line);
String[] tokens = line.split("\\s+");
if (tokens.length != 2) {
throw new IllegalStateException(
String.format("error parsing query results, expected 2 values, found %s: %s",
tokens.length, line));
}
GroupURI groupURI = new GroupURI(URI.create(tokens[0]));
Integer gid = Integer.parseInt(tokens[1]);
posixGroups.add(new PosixGroup(gid, groupURI));
posixGroups.add(tsvPosixGroupParser.parse(line));
}
return posixGroups;
}
}

private URL getServiceURL(URI standardID)
throws IOException, ResourceNotFoundException {
private URL getServiceURL(URI standardID) {
// this probably failed in ctor already
if (capabilities == null) {
throw new RuntimeException("BUG: capabilities not found and went undetected");
Expand All @@ -315,4 +370,36 @@ private URL getServiceURL(URI standardID)
return iface.getAccessURL().getURL();
}

private static class TSVPosixGroupParser {
final PosixGroup parse(final String line) {
log.debug("line: " + line);
final String[] tokens = line.split("\\s+");
if (tokens.length != 2) {
throw new IllegalStateException(
String.format("error parsing query results, expected 2 values, found %s: %s",
tokens.length, line));
}

final GroupURI groupURI = new GroupURI(URI.create(tokens[0]));
final Integer gid = Integer.parseInt(tokens[1]);

return new PosixGroup(gid, groupURI);
}
}

private static class TSVPosixPrincipalParser {
final PosixPrincipal parse(final String line) {
log.debug("line: " + line);
final String[] tokens = line.split("\\s+");
if (tokens.length != 3) {
throw new IllegalStateException(
String.format("error parsing query results, expected 3 values, found %s: %s",
tokens.length, line));
}
final PosixPrincipal posixPrincipal = new PosixPrincipal(Integer.parseInt(tokens[1]));
posixPrincipal.username = tokens[0];
posixPrincipal.defaultGroup = Integer.parseInt(tokens[2]);
return posixPrincipal;
}
}
}
6 changes: 3 additions & 3 deletions cadc-gms/src/main/java/org/opencadc/gms/GroupUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,11 @@ public class GroupUtil {
* Construct a GMS Client from the classpath or fallback to
* the default, no-op implementation if a GMS Client has not
* been configured. Classpath loaded implementations
* must provide a contructor that takes the service URI as
* must provide a constructor that takes the service URI as
* an argument.
*
* <p></p>
* @param serviceID GMS Service ID. If null, the default no-op
* implementation of GMS Client is returned.
* implementation of GMS Client is returned.
*
* @return A GMSClient instance.
*/
Expand Down
3 changes: 1 addition & 2 deletions cadc-gms/src/main/java/org/opencadc/gms/NoOpGroupClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,10 @@
import java.util.List;

/**
*
* This is the default implementation of GroupClient that performs no group membership
* operations. It allows libraries to use the GroupClient without requiring a
* Groups or a GMS implementation.
*
* <p></p>
* This client will be created by GroupClient.getGroupClient() when another implementation
* is not discovered in the classpath.
*
Expand Down
Loading