use of com.zimbra.common.account.Key.AccountBy in project zm-mailbox by Zimbra.
the class Auth method handle.
@Override
public Element handle(Element request, Map<String, Object> context) throws ServiceException {
ZimbraSoapContext zsc = getZimbraSoapContext(context);
Provisioning prov = Provisioning.getInstance();
// Look up the specified account. It is optional in the <authToken> case.
String acctValuePassedIn = null, acctValue = null, acctByStr = null;
AccountBy acctBy = null;
Account acct = null;
Element acctEl = request.getOptionalElement(AccountConstants.E_ACCOUNT);
boolean csrfSupport = request.getAttributeBool(AccountConstants.A_CSRF_SUPPORT, false);
if (acctEl != null) {
acctValuePassedIn = acctEl.getText();
acctValue = acctValuePassedIn;
acctByStr = acctEl.getAttribute(AccountConstants.A_BY, AccountBy.name.name());
acctBy = AccountBy.fromString(acctByStr);
if (acctBy == AccountBy.name) {
Element virtualHostEl = request.getOptionalElement(AccountConstants.E_VIRTUAL_HOST);
String virtualHost = virtualHostEl == null ? null : virtualHostEl.getText().toLowerCase();
if (virtualHost != null && acctValue.indexOf('@') == -1) {
Domain d = prov.get(Key.DomainBy.virtualHostname, virtualHost);
if (d != null)
acctValue = acctValue + "@" + d.getName();
}
}
acct = prov.get(acctBy, acctValue);
}
TrustedDeviceToken trustedToken = null;
if (acct != null) {
TrustedDevices trustedDeviceManager = TwoFactorAuth.getFactory().getTrustedDevices(acct);
if (trustedDeviceManager != null) {
trustedToken = trustedDeviceManager.getTokenFromRequest(request, context);
if (trustedToken != null && trustedToken.isExpired()) {
TrustedDevice device = trustedDeviceManager.getTrustedDeviceByTrustedToken(trustedToken);
if (device != null) {
device.revoke();
}
}
}
}
String password = request.getAttribute(AccountConstants.E_PASSWORD, null);
boolean generateDeviceId = request.getAttributeBool(AccountConstants.A_GENERATE_DEVICE_ID, false);
String twoFactorCode = request.getAttribute(AccountConstants.E_TWO_FACTOR_CODE, null);
String newDeviceId = generateDeviceId ? UUIDUtil.generateUUID() : null;
Element authTokenEl = request.getOptionalElement(AccountConstants.E_AUTH_TOKEN);
if (authTokenEl != null) {
boolean verifyAccount = authTokenEl.getAttributeBool(AccountConstants.A_VERIFY_ACCOUNT, false);
if (verifyAccount && acctEl == null) {
throw ServiceException.INVALID_REQUEST("missing required element: " + AccountConstants.E_ACCOUNT, null);
}
try {
AuthToken at = AuthProvider.getAuthToken(authTokenEl, acct);
addAccountToLogContextByAuthToken(prov, at);
// so the account will show in log context
if (!checkPasswordSecurity(context))
throw ServiceException.INVALID_REQUEST("clear text password is not allowed", null);
AuthToken.Usage usage = at.getUsage();
if (usage != Usage.AUTH && usage != Usage.TWO_FACTOR_AUTH) {
throw AuthFailedServiceException.AUTH_FAILED("invalid auth token");
}
Account authTokenAcct = AuthProvider.validateAuthToken(prov, at, false, usage);
if (verifyAccount) {
// can treat the auth token as an opaque string.
if (acct == null || !acct.getId().equalsIgnoreCase(authTokenAcct.getId())) {
throw new AuthTokenException("auth token doesn't match the named account");
}
}
if (usage == Usage.AUTH) {
ServletRequest httpReq = (ServletRequest) context.get(SoapServlet.SERVLET_REQUEST);
httpReq.setAttribute(CsrfFilter.AUTH_TOKEN, at);
if (csrfSupport && !at.isCsrfTokenEnabled()) {
// handle case where auth token was originally generated with csrf support
// and now client sends the same auth token but saying csrfSupport is turned off
// in that case do not disable CSRF check for this authToken.
at.setCsrfTokenEnabled(csrfSupport);
}
return doResponse(request, at, zsc, context, authTokenAcct, csrfSupport, trustedToken, newDeviceId);
} else {
acct = authTokenAcct;
}
} catch (AuthTokenException e) {
throw ServiceException.AUTH_REQUIRED();
}
}
if (!checkPasswordSecurity(context)) {
throw ServiceException.INVALID_REQUEST("clear text password is not allowed", null);
}
Element preAuthEl = request.getOptionalElement(AccountConstants.E_PREAUTH);
String deviceId = request.getAttribute(AccountConstants.E_DEVICE_ID, null);
long expires = 0;
Map<String, Object> authCtxt = new HashMap<String, Object>();
authCtxt.put(AuthContext.AC_ORIGINATING_CLIENT_IP, context.get(SoapEngine.ORIG_REQUEST_IP));
authCtxt.put(AuthContext.AC_REMOTE_IP, context.get(SoapEngine.SOAP_REQUEST_IP));
authCtxt.put(AuthContext.AC_ACCOUNT_NAME_PASSEDIN, acctValuePassedIn);
authCtxt.put(AuthContext.AC_USER_AGENT, zsc.getUserAgent());
boolean acctAutoProvisioned = false;
if (acct == null) {
// try LAZY auto provision if it is enabled
if (acctBy == AccountBy.name || acctBy == AccountBy.krb5Principal) {
try {
if (acctBy == AccountBy.name) {
EmailAddress email = new EmailAddress(acctValue, false);
String domainName = email.getDomain();
Domain domain = domainName == null ? null : prov.get(Key.DomainBy.name, domainName);
if (password != null) {
acct = prov.autoProvAccountLazy(domain, acctValuePassedIn, password, null);
} else if (preAuthEl != null) {
long timestamp = preAuthEl.getAttributeLong(AccountConstants.A_TIMESTAMP);
expires = preAuthEl.getAttributeLong(AccountConstants.A_EXPIRES, 0);
String preAuth = preAuthEl.getTextTrim();
prov.preAuthAccount(domain, acctValue, acctByStr, timestamp, expires, preAuth, authCtxt);
acct = prov.autoProvAccountLazy(domain, acctValuePassedIn, null, AutoProvAuthMech.PREAUTH);
}
} else {
if (password != null) {
Domain domain = Krb5Principal.getDomainByKrb5Principal(acctValuePassedIn);
if (domain != null) {
acct = prov.autoProvAccountLazy(domain, acctValuePassedIn, password, null);
}
}
}
if (acct != null) {
acctAutoProvisioned = true;
}
} catch (AuthFailedServiceException e) {
ZimbraLog.account.debug("auth failed, unable to auto provisioing acct " + acctValue, e);
} catch (ServiceException e) {
ZimbraLog.account.info("unable to auto provisioing acct " + acctValue, e);
}
}
}
if (acct == null) {
// try ZMG Proxy auto provision if it is enabled
if (acctBy == AccountBy.name && password != null) {
Pair<Account, Boolean> result = null;
try {
result = prov.autoProvZMGProxyAccount(acctValuePassedIn, password);
} catch (AuthFailedServiceException e) {
// Most likely in error with user creds
} catch (ServiceException e) {
ZimbraLog.account.info("unable to auto provision acct " + acctValuePassedIn, e);
}
if (result != null) {
acct = result.getFirst();
acctAutoProvisioned = result.getSecond();
}
}
}
if (acct == null) {
throw AuthFailedServiceException.AUTH_FAILED(acctValue, acctValuePassedIn, "account not found");
}
AccountUtil.addAccountToLogContext(prov, acct.getId(), ZimbraLog.C_NAME, ZimbraLog.C_ID, null);
Boolean registerTrustedDevice = false;
TwoFactorAuth twoFactorManager = TwoFactorAuth.getFactory().getTwoFactorAuth(acct);
if (twoFactorManager.twoFactorAuthEnabled()) {
registerTrustedDevice = trustedToken == null && request.getAttributeBool(AccountConstants.A_TRUSTED_DEVICE, false);
}
// if account was auto provisioned, we had already authenticated the principal
if (!acctAutoProvisioned) {
boolean trustedDeviceOverride = false;
if (trustedToken != null && acct.isFeatureTrustedDevicesEnabled()) {
if (trustedToken.isExpired()) {
ZimbraLog.account.debug("trusted token is expired");
registerTrustedDevice = false;
} else {
Map<String, Object> attrs = getTrustedDeviceAttrs(zsc, deviceId);
try {
verifyTrustedDevice(acct, trustedToken, attrs);
trustedDeviceOverride = true;
} catch (AuthFailedServiceException e) {
ZimbraLog.account.info("trusted device not verified");
}
}
}
boolean usingTwoFactorAuth = acct != null && twoFactorManager.twoFactorAuthRequired() && !trustedDeviceOverride;
boolean twoFactorAuthWithToken = usingTwoFactorAuth && authTokenEl != null;
if (password != null || twoFactorAuthWithToken) {
// authentication logic can be reached with either a password, or a 2FA auth token
if (usingTwoFactorAuth && twoFactorCode == null && password != null) {
int mtaAuthPort = acct.getServer().getMtaAuthPort();
boolean supportsAppSpecificPaswords = acct.isFeatureAppSpecificPasswordsEnabled() && zsc.getPort() == mtaAuthPort;
if (supportsAppSpecificPaswords && password != null) {
// if we are here, it means we are authenticating SMTP,
// so app-specific passwords are accepted. Other protocols (pop, imap)
// doesn't touch this code, so their authentication happens in ZimbraAuth.
AppSpecificPasswords appPasswords = TwoFactorAuth.getFactory().getAppSpecificPasswords(acct, acctValuePassedIn);
appPasswords.authenticate(password);
} else {
prov.authAccount(acct, password, AuthContext.Protocol.soap, authCtxt);
return needTwoFactorAuth(acct, twoFactorManager, zsc);
}
} else {
if (password != null) {
prov.authAccount(acct, password, AuthContext.Protocol.soap, authCtxt);
} else {
// it's ok to not have a password if the client is using a 2FA auth token for the 2nd step of 2FA
if (!twoFactorAuthWithToken) {
throw ServiceException.AUTH_REQUIRED();
}
}
if (usingTwoFactorAuth) {
// check that 2FA has been enabled, in case the client is passing in a twoFactorCode prior to setting up 2FA
if (!twoFactorManager.twoFactorAuthEnabled()) {
throw AccountServiceException.TWO_FACTOR_SETUP_REQUIRED();
}
AuthToken twoFactorToken = null;
if (password == null) {
try {
twoFactorToken = AuthProvider.getAuthToken(authTokenEl, acct);
Account twoFactorTokenAcct = AuthProvider.validateAuthToken(prov, twoFactorToken, false, Usage.TWO_FACTOR_AUTH);
boolean verifyAccount = authTokenEl.getAttributeBool(AccountConstants.A_VERIFY_ACCOUNT, false);
if (verifyAccount && !twoFactorTokenAcct.getId().equalsIgnoreCase(acct.getId())) {
throw new AuthTokenException("two-factor auth token doesn't match the named account");
}
} catch (AuthTokenException e) {
throw AuthFailedServiceException.AUTH_FAILED("bad auth token");
}
}
TwoFactorAuth manager = TwoFactorAuth.getFactory().getTwoFactorAuth(acct);
if (twoFactorCode != null) {
manager.authenticate(twoFactorCode);
} else {
throw AuthFailedServiceException.AUTH_FAILED("no two-factor code provided");
}
if (twoFactorToken != null) {
try {
twoFactorToken.deRegister();
} catch (AuthTokenException e) {
throw ServiceException.FAILURE("cannot de-register two-factor auth token", e);
}
}
}
}
} else if (preAuthEl != null) {
long timestamp = preAuthEl.getAttributeLong(AccountConstants.A_TIMESTAMP);
expires = preAuthEl.getAttributeLong(AccountConstants.A_EXPIRES, 0);
String preAuth = preAuthEl.getTextTrim();
prov.preAuthAccount(acct, acctValue, acctByStr, timestamp, expires, preAuth, authCtxt);
} else {
throw ServiceException.INVALID_REQUEST("must specify " + AccountConstants.E_PASSWORD, null);
}
}
AuthToken at = expires == 0 ? AuthProvider.getAuthToken(acct) : AuthProvider.getAuthToken(acct, expires);
if (registerTrustedDevice && (trustedToken == null || trustedToken.isExpired())) {
//generate a new trusted device token if there is no existing one or if the current one is no longer valid
Map<String, Object> attrs = getTrustedDeviceAttrs(zsc, newDeviceId == null ? deviceId : newDeviceId);
TrustedDevices trustedDeviceManager = TwoFactorAuth.getFactory().getTrustedDevices(acct);
trustedToken = trustedDeviceManager.registerTrustedDevice(attrs);
}
ServletRequest httpReq = (ServletRequest) context.get(SoapServlet.SERVLET_REQUEST);
// For CSRF filter so that token generation can happen
if (csrfSupport && !at.isCsrfTokenEnabled()) {
// handle case where auth token was originally generated with csrf support
// and now client sends the same auth token but saying csrfSupport is turned off
// in that case do not disable CSRF check for this authToken.
at.setCsrfTokenEnabled(csrfSupport);
}
httpReq.setAttribute(CsrfFilter.AUTH_TOKEN, at);
return doResponse(request, at, zsc, context, acct, csrfSupport, trustedToken, newDeviceId);
}
use of com.zimbra.common.account.Key.AccountBy in project zm-mailbox by Zimbra.
the class Auth method handle.
@Override
public Element handle(Element request, Map<String, Object> context) throws ServiceException {
ZimbraSoapContext zsc = getZimbraSoapContext(context);
AuthToken at = null;
Account acct = null;
Provisioning prov = Provisioning.getInstance();
boolean csrfSupport = request.getAttributeBool(AccountConstants.A_CSRF_SUPPORT, false);
String name = request.getAttribute(AdminConstants.E_NAME, null);
Element acctEl = request.getOptionalElement(AccountConstants.E_ACCOUNT);
//only perform auth-token authentication if other credentials are not provided
if (name == null && acctEl == null) {
//get an auth token from cookie
at = zsc.getAuthToken();
if (at == null) {
//if auth token is not in the cookie check for auth token in SOAP
Element authTokenEl = request.getOptionalElement(AdminConstants.E_AUTH_TOKEN);
if (authTokenEl != null) {
try {
at = AuthProvider.getAuthToken(request, new HashMap<String, Object>());
} catch (AuthTokenException e) {
throw ServiceException.AUTH_REQUIRED();
}
}
}
if (at == null) {
//neither login credentials nor valid auth token could be retrieved
throw ServiceException.AUTH_REQUIRED();
}
com.zimbra.cs.service.account.Auth.addAccountToLogContextByAuthToken(prov, at);
if (at.isExpired())
throw ServiceException.AUTH_EXPIRED();
if (!at.isRegistered())
throw ServiceException.AUTH_EXPIRED("authtoken is invalid");
// make sure that the authenticated account is active and has not been deleted/disabled since the last request
acct = prov.get(AccountBy.id, at.getAccountId(), at);
if (acct == null || !acct.getAccountStatus(prov).equals(Provisioning.ACCOUNT_STATUS_ACTIVE))
throw ServiceException.AUTH_EXPIRED();
// make sure the authenticated account is an admin account
checkAdmin(acct);
} else {
/*
* only one of
* <name>...</name>
* or
* <account by="name|id|foreignPrincipal">...</account>
* can/must be specified
*/
if (name != null && acctEl != null)
throw ServiceException.INVALID_REQUEST("only one of <name> or <account> can be specified", null);
if (name == null && acctEl == null)
throw ServiceException.INVALID_REQUEST("missing <name> or <account>", null);
String password = request.getAttribute(AdminConstants.E_PASSWORD);
String twoFactorCode = request.getAttribute(AccountConstants.E_TWO_FACTOR_CODE, null);
Element virtualHostEl = request.getOptionalElement(AccountConstants.E_VIRTUAL_HOST);
String virtualHost = virtualHostEl == null ? null : virtualHostEl.getText().toLowerCase();
String valuePassedIn;
AccountBy by;
String value;
if (name != null) {
valuePassedIn = name;
by = AccountBy.name;
} else {
valuePassedIn = acctEl.getText();
String byStr = acctEl.getAttribute(AccountConstants.A_BY, AccountBy.name.name());
by = AccountBy.fromString(byStr);
}
value = valuePassedIn;
try {
if (by == AccountBy.name && value.indexOf("@") == -1) {
// first try to get by adminName, which resolves the account under cn=admins,cn=zimbra
// and does not need a domain
acct = prov.get(AccountBy.adminName, value, zsc.getAuthToken());
// not found, try applying virtual host name
if (acct == null) {
if (virtualHost != null) {
Domain d = prov.get(Key.DomainBy.virtualHostname, virtualHost);
if (d != null)
value = value + "@" + d.getName();
}
}
}
if (acct == null)
acct = prov.get(by, value);
if (acct == null)
throw AuthFailedServiceException.AUTH_FAILED(value, valuePassedIn, "account not found");
AccountUtil.addAccountToLogContext(prov, acct.getId(), ZimbraLog.C_NAME, ZimbraLog.C_ID, null);
ZimbraLog.security.info(ZimbraLog.encodeAttrs(new String[] { "cmd", "AdminAuth", "account", value }));
Map<String, Object> authCtxt = new HashMap<String, Object>();
authCtxt.put(AuthContext.AC_ORIGINATING_CLIENT_IP, context.get(SoapEngine.ORIG_REQUEST_IP));
authCtxt.put(AuthContext.AC_REMOTE_IP, context.get(SoapEngine.SOAP_REQUEST_IP));
authCtxt.put(AuthContext.AC_ACCOUNT_NAME_PASSEDIN, valuePassedIn);
authCtxt.put(AuthContext.AC_USER_AGENT, zsc.getUserAgent());
authCtxt.put(AuthContext.AC_AS_ADMIN, Boolean.TRUE);
prov.authAccount(acct, password, AuthContext.Protocol.soap, authCtxt);
TwoFactorAuth twoFactorAuth = TwoFactorAuth.getFactory().getTwoFactorAuth(acct);
boolean usingTwoFactorAuth = twoFactorAuth.twoFactorAuthEnabled();
if (usingTwoFactorAuth) {
if (twoFactorCode != null) {
twoFactorAuth.authenticate(twoFactorCode);
}
}
checkAdmin(acct);
AuthMech authedByMech = (AuthMech) authCtxt.get(AuthContext.AC_AUTHED_BY_MECH);
at = AuthProvider.getAuthToken(acct, true, authedByMech);
} catch (ServiceException se) {
ZimbraLog.security.warn(ZimbraLog.encodeAttrs(new String[] { "cmd", "AdminAuth", "account", value, "error", se.getMessage() }));
throw se;
}
}
if (at != null) {
at.setCsrfTokenEnabled(csrfSupport);
}
ServletRequest httpReq = (ServletRequest) context.get(SoapServlet.SERVLET_REQUEST);
httpReq.setAttribute(CsrfFilter.AUTH_TOKEN, at);
return doResponse(request, at, zsc, context, acct, csrfSupport);
}
use of com.zimbra.common.account.Key.AccountBy in project zm-mailbox by Zimbra.
the class CheckPermission method handle.
public Element handle(Element request, Map<String, Object> context) throws ServiceException {
ZimbraSoapContext zsc = getZimbraSoapContext(context);
Provisioning prov = Provisioning.getInstance();
Element eTarget = request.getElement(MailConstants.E_TARGET);
String targetType = eTarget.getAttribute(MailConstants.A_TARGET_TYPE);
TargetType tt = TargetType.fromCode(targetType);
String targetBy = eTarget.getAttribute(MailConstants.A_TARGET_BY);
String targetValue = eTarget.getText();
NamedEntry entry = null;
Element response = zsc.createElement(MailConstants.CHECK_PERMISSION_RESPONSE);
if (TargetType.account == tt) {
AccountBy acctBy = AccountBy.fromString(targetBy);
entry = prov.get(acctBy, targetValue, zsc.getAuthToken());
if (entry == null && acctBy == AccountBy.id) {
throw AccountServiceException.NO_SUCH_ACCOUNT(targetValue);
}
// otherwise, the target could be an external user, let it fall through
// to return the default permission.
} else if (TargetType.calresource == tt) {
Key.CalendarResourceBy crBy = Key.CalendarResourceBy.fromString(targetBy);
entry = prov.get(crBy, targetValue);
if (entry == null && crBy == Key.CalendarResourceBy.id) {
throw AccountServiceException.NO_SUCH_CALENDAR_RESOURCE(targetValue);
}
} else if (TargetType.dl == tt) {
Key.DistributionListBy dlBy = Key.DistributionListBy.fromString(targetBy);
entry = prov.getGroupBasic(dlBy, targetValue);
if (entry == null && dlBy == Key.DistributionListBy.id) {
throw AccountServiceException.NO_SUCH_CALENDAR_RESOURCE(targetValue);
}
} else {
throw ServiceException.INVALID_REQUEST("invalid target type: " + targetType, null);
}
List<UserRight> rights = new ArrayList<UserRight>();
for (Element eRight : request.listElements(MailConstants.E_RIGHT)) {
UserRight r = RightManager.getInstance().getUserRight(eRight.getText());
rights.add(r);
}
boolean finalResult = true;
AccessManager am = AccessManager.getInstance();
for (UserRight right : rights) {
boolean allow = am.canDo(zsc.getAuthToken(), entry, right, false);
if (allow && DiscoverRights.isDelegatedSendRight(right) && TargetBy.name.name().equals(targetBy)) {
allow = AccountUtil.isAllowedSendAddress(entry, targetValue);
}
response.addElement(MailConstants.E_RIGHT).addAttribute(MailConstants.A_ALLOW, allow).setText(right.getName());
finalResult = finalResult & allow;
}
return returnResponse(response, finalResult);
}
use of com.zimbra.common.account.Key.AccountBy in project zm-mailbox by Zimbra.
the class AddAccountLogger method getAccountFromLoggerRequest.
/**
* Returns the <tt>Account</tt> object based on the <id> or <account>
* element owned by the given request element.
*/
static Account getAccountFromLoggerRequest(Element request) throws ServiceException {
Account account = null;
Provisioning prov = Provisioning.getInstance();
Element idElement = request.getOptionalElement(AdminConstants.E_ID);
if (idElement != null) {
// Handle deprecated <id> element.
ZimbraLog.soap.info("The <%s> element is deprecated for <%s>. Use <%s> instead.", AdminConstants.E_ID, request.getName(), AdminConstants.E_ACCOUNT);
String id = idElement.getText();
account = prov.get(AccountBy.id, id);
if (account == null) {
throw AccountServiceException.NO_SUCH_ACCOUNT(idElement.getText());
}
} else {
// Handle <account> element.
Element accountElement = request.getElement(AdminConstants.E_ACCOUNT);
AccountBy by = AccountBy.fromString(accountElement.getAttribute(AdminConstants.A_BY));
account = prov.get(by, accountElement.getText());
if (account == null) {
throw AccountServiceException.NO_SUCH_ACCOUNT(accountElement.getText());
}
}
return account;
}
use of com.zimbra.common.account.Key.AccountBy in project zm-mailbox by Zimbra.
the class GetShareInfo method handle.
@Override
public Element handle(Element request, Map<String, Object> context) throws ServiceException {
ZimbraSoapContext zsc = getZimbraSoapContext(context);
OperationContext octxt = getOperationContext(zsc, context);
Provisioning prov = Provisioning.getInstance();
GetShareInfoRequest req = JaxbUtil.elementToJaxb(request);
GranteeChooser granteeChooser = req.getGrantee();
byte granteeType = com.zimbra.cs.service.account.GetShareInfo.getGranteeType(granteeChooser == null ? null : granteeChooser.getType());
String granteeId = null;
String granteeName = null;
if (granteeChooser != null) {
granteeId = granteeChooser.getId();
granteeName = granteeChooser.getName();
}
Account ownerAcct = null;
AccountBy acctBy = req.getOwner().getBy().toKeyAccountBy();
String accountSelectorKey = req.getOwner().getKey();
ownerAcct = prov.get(acctBy, accountSelectorKey);
// in the account namespace GetShareInfo
// to defend against harvest attacks return "no shares" instead of error when an invalid user name/id is used.
// this is the admin namespace GetShareInfo, we want to let the admin know if the owner name is bad
// unless the admin is a domain admin for a different domain...
defendAgainstAccountOrCalendarResourceHarvesting(ownerAcct, acctBy, accountSelectorKey, zsc, Admin.R_adminLoginAs, Admin.R_adminLoginCalendarResourceAs);
GetShareInfoResponse response = new GetShareInfoResponse();
ResultFilter resultFilter = new ResultFilterByTarget(granteeId, granteeName);
ShareInfoVisitor visitor = new ShareInfoVisitor(prov, response, null, resultFilter);
ShareInfo.Discover.discover(octxt, prov, null, granteeType, ownerAcct, visitor);
visitor.finish();
return zsc.jaxbToElement(response);
}
Aggregations