Skip to content

Commit

Permalink
Merge pull request #159 from at88mph/full-mapping
Browse files Browse the repository at this point in the history
Implement UserMap and GroupMap output.
  • Loading branch information
pdowler authored Oct 23, 2023
2 parents dd7df13 + d3a6591 commit 5b59ec5
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 50 deletions.
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

0 comments on commit 5b59ec5

Please sign in to comment.