mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2024-11-22 06:12:05 +01:00
Added support for pre-approved subscription requests (RFC 6121 § 3.4)
SMACK-639
This commit is contained in:
parent
f410ece468
commit
fae9d129a9
12 changed files with 440 additions and 5 deletions
|
@ -1405,7 +1405,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
return getFeature(element, namespace) != null;
|
return getFeature(element, namespace) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addStreamFeature(ExtensionElement feature) {
|
protected void addStreamFeature(ExtensionElement feature) {
|
||||||
String key = XmppStringUtils.generateKey(feature.getElementName(), feature.getNamespace());
|
String key = XmppStringUtils.generateKey(feature.getElementName(), feature.getNamespace());
|
||||||
streamFeatures.put(key, feature);
|
streamFeatures.put(key, feature);
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||||
import org.jivesoftware.smack.packet.Stanza;
|
import org.jivesoftware.smack.packet.Stanza;
|
||||||
import org.jivesoftware.smack.packet.PlainStreamElement;
|
import org.jivesoftware.smack.packet.PlainStreamElement;
|
||||||
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
||||||
|
@ -191,6 +192,16 @@ public class DummyConnection extends AbstractXMPPConnection {
|
||||||
invokePacketCollectorsAndNotifyRecvListeners(packet);
|
invokePacketCollectorsAndNotifyRecvListeners(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable stream feature.
|
||||||
|
*
|
||||||
|
* @param streamFeature the stream feature.
|
||||||
|
* @since 4.2
|
||||||
|
*/
|
||||||
|
public void enableStreamFeature(ExtensionElement streamFeature) {
|
||||||
|
addStreamFeature(streamFeature);
|
||||||
|
}
|
||||||
|
|
||||||
public static DummyConnection newConnectedDummyConnection() {
|
public static DummyConnection newConnectedDummyConnection() {
|
||||||
DummyConnection dummyConnection = new DummyConnection();
|
DummyConnection dummyConnection = new DummyConnection();
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -40,6 +40,7 @@ import org.jivesoftware.smack.Manager;
|
||||||
import org.jivesoftware.smack.StanzaListener;
|
import org.jivesoftware.smack.StanzaListener;
|
||||||
import org.jivesoftware.smack.SmackException;
|
import org.jivesoftware.smack.SmackException;
|
||||||
import org.jivesoftware.smack.XMPPConnection;
|
import org.jivesoftware.smack.XMPPConnection;
|
||||||
|
import org.jivesoftware.smack.SmackException.FeatureNotSupportedException;
|
||||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||||
import org.jivesoftware.smack.SmackException.NotLoggedInException;
|
import org.jivesoftware.smack.SmackException.NotLoggedInException;
|
||||||
|
@ -57,6 +58,7 @@ import org.jivesoftware.smack.packet.XMPPError.Condition;
|
||||||
import org.jivesoftware.smack.roster.packet.RosterPacket;
|
import org.jivesoftware.smack.roster.packet.RosterPacket;
|
||||||
import org.jivesoftware.smack.roster.packet.RosterVer;
|
import org.jivesoftware.smack.roster.packet.RosterVer;
|
||||||
import org.jivesoftware.smack.roster.packet.RosterPacket.Item;
|
import org.jivesoftware.smack.roster.packet.RosterPacket.Item;
|
||||||
|
import org.jivesoftware.smack.roster.packet.SubscriptionPreApproval;
|
||||||
import org.jivesoftware.smack.roster.rosterstore.RosterStore;
|
import org.jivesoftware.smack.roster.rosterstore.RosterStore;
|
||||||
import org.jivesoftware.smack.util.Objects;
|
import org.jivesoftware.smack.util.Objects;
|
||||||
import org.jxmpp.jid.BareJid;
|
import org.jxmpp.jid.BareJid;
|
||||||
|
@ -456,6 +458,7 @@ public class Roster extends Manager {
|
||||||
* @param name the nickname of the user.
|
* @param name the nickname of the user.
|
||||||
* @param groups the list of group names the entry will belong to, or <tt>null</tt> if the
|
* @param groups the list of group names the entry will belong to, or <tt>null</tt> if the
|
||||||
* the roster entry won't belong to a group.
|
* the roster entry won't belong to a group.
|
||||||
|
* @param approved the pre-approval state.
|
||||||
* @throws NoResponseException if there was no response from the server.
|
* @throws NoResponseException if there was no response from the server.
|
||||||
* @throws XMPPErrorException if an XMPP exception occurs.
|
* @throws XMPPErrorException if an XMPP exception occurs.
|
||||||
* @throws NotLoggedInException If not logged in.
|
* @throws NotLoggedInException If not logged in.
|
||||||
|
@ -491,6 +494,68 @@ public class Roster extends Manager {
|
||||||
connection.sendStanza(presencePacket);
|
connection.sendStanza(presencePacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new pre-approved roster entry and presence subscription. The server will
|
||||||
|
* asynchronously update the roster with the subscription status.
|
||||||
|
*
|
||||||
|
* @param user the user. (e.g. johndoe@jabber.org)
|
||||||
|
* @param name the nickname of the user.
|
||||||
|
* @param groups the list of group names the entry will belong to, or <tt>null</tt> if the
|
||||||
|
* the roster entry won't belong to a group.
|
||||||
|
* @param approved the pre-approval state.
|
||||||
|
* @throws NoResponseException if there was no response from the server.
|
||||||
|
* @throws XMPPErrorException if an XMPP exception occurs.
|
||||||
|
* @throws NotLoggedInException if not logged in.
|
||||||
|
* @throws NotConnectedException
|
||||||
|
* @throws InterruptedException
|
||||||
|
* @throws FeatureNotSupportedException if pre-approving is not supported.
|
||||||
|
* @since 4.2
|
||||||
|
*/
|
||||||
|
public void preApproveAndCreateEntry(Jid user, String name, String[] groups) throws NotLoggedInException, NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, FeatureNotSupportedException {
|
||||||
|
preApprove(user);
|
||||||
|
createEntry(user, name, groups);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pre-approve user presence subscription.
|
||||||
|
*
|
||||||
|
* @param user the user. (e.g. johndoe@jabber.org)
|
||||||
|
* @throws NotLoggedInException if not logged in.
|
||||||
|
* @throws NotConnectedException
|
||||||
|
* @throws InterruptedException
|
||||||
|
* @throws FeatureNotSupportedException if pre-approving is not supported.
|
||||||
|
* @since 4.2
|
||||||
|
*/
|
||||||
|
public void preApprove(Jid user) throws NotLoggedInException, NotConnectedException, InterruptedException, FeatureNotSupportedException {
|
||||||
|
final XMPPConnection connection = connection();
|
||||||
|
if (!isSubscriptionPreApprovalSupported()) {
|
||||||
|
throw new FeatureNotSupportedException("Pre-approving");
|
||||||
|
}
|
||||||
|
|
||||||
|
Presence presencePacket = new Presence(Presence.Type.subscribed);
|
||||||
|
presencePacket.setTo(user);
|
||||||
|
connection.sendStanza(presencePacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for subscription pre-approval support.
|
||||||
|
*
|
||||||
|
* @return true if subscription pre-approval is supported by the server.
|
||||||
|
* @throws NotLoggedInException if not logged in.
|
||||||
|
* @since 4.2
|
||||||
|
*/
|
||||||
|
public boolean isSubscriptionPreApprovalSupported() throws NotLoggedInException {
|
||||||
|
final XMPPConnection connection = connection();
|
||||||
|
if (!connection.isAuthenticated()) {
|
||||||
|
throw new NotLoggedInException();
|
||||||
|
}
|
||||||
|
if (connection.isAnonymous()) {
|
||||||
|
throw new IllegalStateException("Anonymous users can't have a roster.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return connection.hasFeature(SubscriptionPreApproval.ELEMENT, SubscriptionPreApproval.NAMESPACE);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a roster entry from the roster. The roster entry will also be removed from the
|
* Removes a roster entry from the roster. The roster entry will also be removed from the
|
||||||
* unfiled entries or from any roster group where it could belong and will no longer be part
|
* unfiled entries or from any roster group where it could belong and will no longer be part
|
||||||
|
@ -1329,7 +1394,7 @@ public class Roster extends Manager {
|
||||||
|
|
||||||
for (RosterPacket.Item item : validItems) {
|
for (RosterPacket.Item item : validItems) {
|
||||||
RosterEntry entry = new RosterEntry(item.getUser(), item.getName(),
|
RosterEntry entry = new RosterEntry(item.getUser(), item.getName(),
|
||||||
item.getItemType(), item.getItemStatus(), Roster.this, connection);
|
item.getItemType(), item.getItemStatus(), item.isApproved(), Roster.this, connection);
|
||||||
addUpdateEntry(addedEntries, updatedEntries, unchangedEntries, item, entry);
|
addUpdateEntry(addedEntries, updatedEntries, unchangedEntries, item, entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1359,7 +1424,7 @@ public class Roster extends Manager {
|
||||||
// await possible further roster pushes.
|
// await possible further roster pushes.
|
||||||
for (RosterPacket.Item item : rosterStore.getEntries()) {
|
for (RosterPacket.Item item : rosterStore.getEntries()) {
|
||||||
RosterEntry entry = new RosterEntry(item.getUser(), item.getName(),
|
RosterEntry entry = new RosterEntry(item.getUser(), item.getName(),
|
||||||
item.getItemType(), item.getItemStatus(), Roster.this, connection);
|
item.getItemType(), item.getItemStatus(), item.isApproved(), Roster.this, connection);
|
||||||
addUpdateEntry(addedEntries, updatedEntries, unchangedEntries, item, entry);
|
addUpdateEntry(addedEntries, updatedEntries, unchangedEntries, item, entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1429,7 +1494,7 @@ public class Roster extends Manager {
|
||||||
// safely retrieve this single item here.
|
// safely retrieve this single item here.
|
||||||
Item item = items.iterator().next();
|
Item item = items.iterator().next();
|
||||||
RosterEntry entry = new RosterEntry(item.getUser(), item.getName(),
|
RosterEntry entry = new RosterEntry(item.getUser(), item.getName(),
|
||||||
item.getItemType(), item.getItemStatus(), Roster.this, connection);
|
item.getItemType(), item.getItemStatus(), item.isApproved(), Roster.this, connection);
|
||||||
String version = rosterPacket.getVersion();
|
String version = rosterPacket.getVersion();
|
||||||
|
|
||||||
if (item.getItemType().equals(RosterPacket.ItemType.remove)) {
|
if (item.getItemType().equals(RosterPacket.ItemType.remove)) {
|
||||||
|
|
|
@ -47,6 +47,7 @@ public class RosterEntry {
|
||||||
private String name;
|
private String name;
|
||||||
private RosterPacket.ItemType type;
|
private RosterPacket.ItemType type;
|
||||||
private RosterPacket.ItemStatus status;
|
private RosterPacket.ItemStatus status;
|
||||||
|
private final boolean approved;
|
||||||
final private Roster roster;
|
final private Roster roster;
|
||||||
final private XMPPConnection connection;
|
final private XMPPConnection connection;
|
||||||
|
|
||||||
|
@ -57,14 +58,16 @@ public class RosterEntry {
|
||||||
* @param name the nickname for the entry.
|
* @param name the nickname for the entry.
|
||||||
* @param type the subscription type.
|
* @param type the subscription type.
|
||||||
* @param status the subscription status (related to subscriptions pending to be approbed).
|
* @param status the subscription status (related to subscriptions pending to be approbed).
|
||||||
|
* @param approved the pre-approval flag.
|
||||||
* @param connection a connection to the XMPP server.
|
* @param connection a connection to the XMPP server.
|
||||||
*/
|
*/
|
||||||
RosterEntry(Jid user, String name, RosterPacket.ItemType type,
|
RosterEntry(Jid user, String name, RosterPacket.ItemType type,
|
||||||
RosterPacket.ItemStatus status, Roster roster, XMPPConnection connection) {
|
RosterPacket.ItemStatus status, boolean approved, Roster roster, XMPPConnection connection) {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.status = status;
|
this.status = status;
|
||||||
|
this.approved = approved;
|
||||||
this.roster = roster;
|
this.roster = roster;
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
}
|
}
|
||||||
|
@ -124,6 +127,15 @@ public class RosterEntry {
|
||||||
this.status = status;
|
this.status = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the pre-approval state of this entry.
|
||||||
|
*
|
||||||
|
* @return the pre-approval state.
|
||||||
|
*/
|
||||||
|
public boolean isApproved() {
|
||||||
|
return approved;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an copied list of the roster groups that this entry belongs to.
|
* Returns an copied list of the roster groups that this entry belongs to.
|
||||||
*
|
*
|
||||||
|
@ -244,6 +256,8 @@ public class RosterEntry {
|
||||||
}
|
}
|
||||||
else if (!user.equals(other.user))
|
else if (!user.equals(other.user))
|
||||||
return false;
|
return false;
|
||||||
|
if (approved != other.approved)
|
||||||
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,6 +265,7 @@ public class RosterEntry {
|
||||||
RosterPacket.Item item = new RosterPacket.Item(entry.getUser(), entry.getName());
|
RosterPacket.Item item = new RosterPacket.Item(entry.getUser(), entry.getName());
|
||||||
item.setItemType(entry.getType());
|
item.setItemType(entry.getType());
|
||||||
item.setItemStatus(entry.getStatus());
|
item.setItemStatus(entry.getStatus());
|
||||||
|
item.setApproved(entry.isApproved());
|
||||||
// Set the correct group names for the item.
|
// Set the correct group names for the item.
|
||||||
for (RosterGroup group : entry.getGroups()) {
|
for (RosterGroup group : entry.getGroups()) {
|
||||||
item.addGroupName(group.getName());
|
item.addGroupName(group.getName());
|
||||||
|
|
|
@ -111,6 +111,7 @@ public class RosterPacket extends IQ {
|
||||||
private String name;
|
private String name;
|
||||||
private ItemType itemType;
|
private ItemType itemType;
|
||||||
private ItemStatus itemStatus;
|
private ItemStatus itemStatus;
|
||||||
|
private boolean approved;
|
||||||
private final Set<String> groupNames;
|
private final Set<String> groupNames;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -190,6 +191,25 @@ public class RosterPacket extends IQ {
|
||||||
this.itemStatus = itemStatus;
|
this.itemStatus = itemStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the roster item pre-approval state.
|
||||||
|
*
|
||||||
|
* @return the pre-approval state.
|
||||||
|
*/
|
||||||
|
public boolean isApproved() {
|
||||||
|
return approved;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the roster item pre-approval state.
|
||||||
|
*
|
||||||
|
* @param approved the pre-approval flag.
|
||||||
|
*/
|
||||||
|
public void setApproved(boolean approved) {
|
||||||
|
this.approved = approved;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an unmodifiable set of the group names that the roster item
|
* Returns an unmodifiable set of the group names that the roster item
|
||||||
* belongs to.
|
* belongs to.
|
||||||
|
@ -224,6 +244,7 @@ public class RosterPacket extends IQ {
|
||||||
xml.optAttribute("name", name);
|
xml.optAttribute("name", name);
|
||||||
xml.optAttribute("subscription", itemType);
|
xml.optAttribute("subscription", itemType);
|
||||||
xml.optAttribute("ask", itemStatus);
|
xml.optAttribute("ask", itemStatus);
|
||||||
|
xml.optBooleanAttribute("approved", approved);
|
||||||
xml.rightAngleBracket();
|
xml.rightAngleBracket();
|
||||||
|
|
||||||
for (String groupName : groupNames) {
|
for (String groupName : groupNames) {
|
||||||
|
@ -242,6 +263,7 @@ public class RosterPacket extends IQ {
|
||||||
result = prime * result + ((itemType == null) ? 0 : itemType.hashCode());
|
result = prime * result + ((itemType == null) ? 0 : itemType.hashCode());
|
||||||
result = prime * result + ((name == null) ? 0 : name.hashCode());
|
result = prime * result + ((name == null) ? 0 : name.hashCode());
|
||||||
result = prime * result + ((user == null) ? 0 : user.hashCode());
|
result = prime * result + ((user == null) ? 0 : user.hashCode());
|
||||||
|
result = prime * result + ((approved == false) ? 0 : 1);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,6 +298,8 @@ public class RosterPacket extends IQ {
|
||||||
}
|
}
|
||||||
else if (!user.equals(other.user))
|
else if (!user.equals(other.user))
|
||||||
return false;
|
return false;
|
||||||
|
if (approved != other.approved)
|
||||||
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright © 2015 Tomáš Havlas
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.jivesoftware.smack.roster.packet;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||||
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
|
||||||
|
public class SubscriptionPreApproval implements ExtensionElement {
|
||||||
|
|
||||||
|
public static final String ELEMENT = "sub";
|
||||||
|
public static final String NAMESPACE = "urn:xmpp:features:pre-approval";
|
||||||
|
|
||||||
|
public static final SubscriptionPreApproval INSTANCE = new SubscriptionPreApproval();
|
||||||
|
|
||||||
|
private SubscriptionPreApproval() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getElementName() {
|
||||||
|
return ELEMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNamespace() {
|
||||||
|
return NAMESPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XmlStringBuilder toXML() {
|
||||||
|
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||||
|
xml.closeEmptyElement();
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -21,6 +21,7 @@ import java.io.IOException;
|
||||||
import org.jivesoftware.smack.SmackException;
|
import org.jivesoftware.smack.SmackException;
|
||||||
import org.jivesoftware.smack.provider.IQProvider;
|
import org.jivesoftware.smack.provider.IQProvider;
|
||||||
import org.jivesoftware.smack.roster.packet.RosterPacket;
|
import org.jivesoftware.smack.roster.packet.RosterPacket;
|
||||||
|
import org.jivesoftware.smack.util.ParserUtils;
|
||||||
import org.jxmpp.jid.Jid;
|
import org.jxmpp.jid.Jid;
|
||||||
import org.jxmpp.jid.impl.JidCreate;
|
import org.jxmpp.jid.impl.JidCreate;
|
||||||
import org.xmlpull.v1.XmlPullParser;
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
|
@ -59,6 +60,9 @@ public class RosterPacketProvider extends IQProvider<RosterPacket> {
|
||||||
String subscription = parser.getAttributeValue("", "subscription");
|
String subscription = parser.getAttributeValue("", "subscription");
|
||||||
RosterPacket.ItemType type = RosterPacket.ItemType.valueOf(subscription != null ? subscription : "none");
|
RosterPacket.ItemType type = RosterPacket.ItemType.valueOf(subscription != null ? subscription : "none");
|
||||||
item.setItemType(type);
|
item.setItemType(type);
|
||||||
|
// Set approval status.
|
||||||
|
boolean approved = ParserUtils.getBooleanAttribute(parser, "approved", false);
|
||||||
|
item.setApproved(approved);
|
||||||
break;
|
break;
|
||||||
case "group":
|
case "group":
|
||||||
// TODO item!= null
|
// TODO item!= null
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright © 2015 Tomáš Havlas
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.jivesoftware.smack.roster.provider;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.SmackException;
|
||||||
|
import org.jivesoftware.smack.provider.ExtensionElementProvider;
|
||||||
|
import org.jivesoftware.smack.roster.packet.SubscriptionPreApproval;
|
||||||
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
|
public class SubscriptionPreApprovalStreamFeatureProvider extends ExtensionElementProvider<SubscriptionPreApproval> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SubscriptionPreApproval parse(XmlPullParser parser, int initialDepth)
|
||||||
|
throws XmlPullParserException, IOException, SmackException {
|
||||||
|
return SubscriptionPreApproval.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -188,6 +188,7 @@ public class DirectoryRosterStore implements RosterStore {
|
||||||
String name = null;
|
String name = null;
|
||||||
String type = null;
|
String type = null;
|
||||||
String status = null;
|
String status = null;
|
||||||
|
String approved = null;
|
||||||
|
|
||||||
List<String> groupNames = new ArrayList<String>();
|
List<String> groupNames = new ArrayList<String>();
|
||||||
|
|
||||||
|
@ -220,6 +221,10 @@ public class DirectoryRosterStore implements RosterStore {
|
||||||
parser.next();
|
parser.next();
|
||||||
status = parser.getText();
|
status = parser.getText();
|
||||||
}
|
}
|
||||||
|
else if (parserName.equals("approved")) {
|
||||||
|
parser.next();
|
||||||
|
approved = parser.getText();
|
||||||
|
}
|
||||||
else if (parserName.equals("group")) {
|
else if (parserName.equals("group")) {
|
||||||
parser.next();
|
parser.next();
|
||||||
parser.next();
|
parser.next();
|
||||||
|
@ -275,6 +280,9 @@ public class DirectoryRosterStore implements RosterStore {
|
||||||
item.setItemStatus(itemStatus);
|
item.setItemStatus(itemStatus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (approved != null) {
|
||||||
|
item.setApproved(Boolean.parseBoolean(approved));
|
||||||
|
}
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
@ -287,6 +295,7 @@ public class DirectoryRosterStore implements RosterStore {
|
||||||
xml.optElement("name", item.getName());
|
xml.optElement("name", item.getName());
|
||||||
xml.optElement("type", item.getItemType());
|
xml.optElement("type", item.getItemType());
|
||||||
xml.optElement("status", item.getItemStatus());
|
xml.optElement("status", item.getItemStatus());
|
||||||
|
xml.optElement("approved", Boolean.toString(item.isApproved()));
|
||||||
for (String groupName : item.getGroupNames()) {
|
for (String groupName : item.getGroupNames()) {
|
||||||
xml.openElement("group");
|
xml.openElement("group");
|
||||||
xml.element("groupName", groupName);
|
xml.element("groupName", groupName);
|
||||||
|
|
|
@ -7,6 +7,12 @@
|
||||||
<className>org.jivesoftware.smack.roster.provider.RosterPacketProvider</className>
|
<className>org.jivesoftware.smack.roster.provider.RosterPacketProvider</className>
|
||||||
</iqProvider>
|
</iqProvider>
|
||||||
|
|
||||||
|
<streamFeatureProvider>
|
||||||
|
<elementName>sub</elementName>
|
||||||
|
<namespace>urn:xmpp:features:pre-approval</namespace>
|
||||||
|
<className>org.jivesoftware.smack.roster.provider.SubscriptionPreApprovalStreamFeatureProvider</className>
|
||||||
|
</streamFeatureProvider>
|
||||||
|
|
||||||
<streamFeatureProvider>
|
<streamFeatureProvider>
|
||||||
<elementName>ver</elementName>
|
<elementName>ver</elementName>
|
||||||
<namespace>urn:xmpp:features:rosterver</namespace>
|
<namespace>urn:xmpp:features:rosterver</namespace>
|
||||||
|
|
|
@ -0,0 +1,207 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright © 2015 Tomáš Havlas
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jivesoftware.smack.roster;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.DummyConnection;
|
||||||
|
import org.jivesoftware.smack.SmackException.FeatureNotSupportedException;
|
||||||
|
import org.jivesoftware.smack.im.InitSmackIm;
|
||||||
|
import org.jivesoftware.smack.packet.IQ;
|
||||||
|
import org.jivesoftware.smack.packet.Stanza;
|
||||||
|
import org.jivesoftware.smack.packet.Presence;
|
||||||
|
import org.jivesoftware.smack.packet.IQ.Type;
|
||||||
|
import org.jivesoftware.smack.roster.RosterTest.TestRosterListener;
|
||||||
|
import org.jivesoftware.smack.roster.packet.RosterPacket;
|
||||||
|
import org.jivesoftware.smack.roster.packet.RosterPacket.Item;
|
||||||
|
import org.jivesoftware.smack.roster.packet.RosterPacket.ItemType;
|
||||||
|
import org.jivesoftware.smack.roster.packet.SubscriptionPreApproval;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.jxmpp.jid.Jid;
|
||||||
|
import org.jxmpp.jid.impl.JidCreate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that verifies the correct behavior of the pre-approval implementation.
|
||||||
|
*
|
||||||
|
* @see <a href="http://xmpp.org/rfcs/rfc6121.html#sub-preapproval">Pre-Approving a Subscription Request</a>
|
||||||
|
* @author Tomáš Havlas
|
||||||
|
*/
|
||||||
|
public class SubscriptionPreApprovalTest extends InitSmackIm {
|
||||||
|
|
||||||
|
private DummyConnection connection;
|
||||||
|
private Roster roster;
|
||||||
|
private TestRosterListener rosterListener;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
connection = new DummyConnection();
|
||||||
|
connection.connect();
|
||||||
|
connection.login();
|
||||||
|
rosterListener = new TestRosterListener();
|
||||||
|
roster = Roster.getInstanceFor(connection);
|
||||||
|
roster.addRosterListener(rosterListener);
|
||||||
|
connection.setPacketReplyTimeout(1000 * 60 * 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() throws Exception {
|
||||||
|
connection.disconnect();
|
||||||
|
connection = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected=FeatureNotSupportedException.class)
|
||||||
|
public void testPreApprovalNotSupported() throws Throwable {
|
||||||
|
final Jid contactJID = JidCreate.from("preapproval@example.com");
|
||||||
|
roster.preApprove(contactJID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPreApproveAndCreate() throws Throwable {
|
||||||
|
final Jid contactJID = JidCreate.from("preapproval@example.com");
|
||||||
|
final String contactName = "PreApproval";
|
||||||
|
final String[] contactGroup = {};
|
||||||
|
connection.enableStreamFeature(SubscriptionPreApproval.INSTANCE);
|
||||||
|
|
||||||
|
final PreApproveAndCreateEntryResponder serverSimulator = new PreApproveAndCreateEntryResponder() {
|
||||||
|
@Override
|
||||||
|
void verifyRosterUpdateRequest(final RosterPacket updateRequest) {
|
||||||
|
final Item item = updateRequest.getRosterItems().iterator().next();
|
||||||
|
assertSame("The provided JID doesn't match the requested!",
|
||||||
|
contactJID,
|
||||||
|
item.getUser());
|
||||||
|
assertSame("The provided name doesn't match the requested!",
|
||||||
|
contactName,
|
||||||
|
item.getName());
|
||||||
|
assertSame("The provided group number doesn't match the requested!",
|
||||||
|
0,
|
||||||
|
item.getGroupNames().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void verifyPreApprovalRequest(Presence preApproval) {
|
||||||
|
assertSame("The provided name doesn't match the requested!",
|
||||||
|
contactJID,
|
||||||
|
preApproval.getTo());
|
||||||
|
assertSame("The provided presence type is incorrect!",
|
||||||
|
Presence.Type.subscribed,
|
||||||
|
preApproval.getType());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
serverSimulator.start();
|
||||||
|
roster.preApproveAndCreateEntry(contactJID, contactName, contactGroup);
|
||||||
|
serverSimulator.join();
|
||||||
|
|
||||||
|
// Check if an error occurred within the simulator
|
||||||
|
final Throwable exception = serverSimulator.getException();
|
||||||
|
if (exception != null) {
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
rosterListener.waitUntilInvocationOrTimeout();
|
||||||
|
|
||||||
|
// Verify the roster entry of the new contact
|
||||||
|
final RosterEntry addedEntry = roster.getEntry(contactJID);
|
||||||
|
assertNotNull("The new contact wasn't added to the roster!", addedEntry);
|
||||||
|
assertTrue("The roster listener wasn't invoked for the new contact!",
|
||||||
|
rosterListener.getAddedAddresses().contains(contactJID));
|
||||||
|
assertSame("Setup wrong name for the new contact!",
|
||||||
|
contactName,
|
||||||
|
addedEntry.getName());
|
||||||
|
assertSame("Setup wrong default subscription status!",
|
||||||
|
ItemType.none,
|
||||||
|
addedEntry.getType());
|
||||||
|
assertSame("The new contact should be member of exactly one group!",
|
||||||
|
0,
|
||||||
|
addedEntry.getGroups().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class can be used to simulate the server response for
|
||||||
|
* a pre approve request request.
|
||||||
|
*/
|
||||||
|
private abstract class PreApproveAndCreateEntryResponder extends Thread {
|
||||||
|
private Throwable exception = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overwrite this method to check if the received roster update request is valid.
|
||||||
|
*
|
||||||
|
* @param updateRequest the request which would be sent to the server.
|
||||||
|
*/
|
||||||
|
abstract void verifyRosterUpdateRequest(final RosterPacket updateRequest);
|
||||||
|
/**
|
||||||
|
* Overwrite this method to check if recieved pre-approval request is valid
|
||||||
|
*
|
||||||
|
* @param preApproval the request which would be sent to server.
|
||||||
|
*/
|
||||||
|
abstract void verifyPreApprovalRequest(final Presence preApproval);
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
final Stanza packet = connection.getSentPacket();
|
||||||
|
if (packet instanceof RosterPacket && ((IQ) packet).getType() == Type.set) {
|
||||||
|
final RosterPacket rosterRequest = (RosterPacket) packet;
|
||||||
|
|
||||||
|
// Prepare and process the roster push
|
||||||
|
final RosterPacket rosterPush = new RosterPacket();
|
||||||
|
final Item item = rosterRequest.getRosterItems().iterator().next();
|
||||||
|
if (item.getItemType() != ItemType.remove) {
|
||||||
|
item.setItemType(ItemType.none);
|
||||||
|
}
|
||||||
|
rosterPush.setType(Type.set);
|
||||||
|
rosterPush.setTo(connection.getUser());
|
||||||
|
rosterPush.addRosterItem(item);
|
||||||
|
connection.processPacket(rosterPush);
|
||||||
|
|
||||||
|
// Create and process the IQ response
|
||||||
|
final IQ response = IQ.createResultIQ(rosterRequest);
|
||||||
|
connection.processPacket(response);
|
||||||
|
|
||||||
|
// Verify the roster update request
|
||||||
|
assertSame("A roster set MUST contain one and only one <item/> element.",
|
||||||
|
1,
|
||||||
|
rosterRequest.getRosterItemCount());
|
||||||
|
verifyRosterUpdateRequest(rosterRequest);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (packet instanceof Presence && ((Presence) packet).getType() == Presence.Type.subscribed) {
|
||||||
|
final Presence approval = (Presence) packet;
|
||||||
|
verifyPreApprovalRequest(approval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Throwable e) {
|
||||||
|
exception = e;
|
||||||
|
fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the exception or error if something went wrong.
|
||||||
|
*
|
||||||
|
* @return the Throwable exception or error that occurred.
|
||||||
|
*/
|
||||||
|
public Throwable getException() {
|
||||||
|
return exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -107,6 +107,8 @@ public class DirectoryRosterStoreTest {
|
||||||
item1.getItemType(), storedItem.getItemType());
|
item1.getItemType(), storedItem.getItemType());
|
||||||
assertEquals("ItemStatus of added entry",
|
assertEquals("ItemStatus of added entry",
|
||||||
item1.getItemStatus(), storedItem.getItemStatus());
|
item1.getItemStatus(), storedItem.getItemStatus());
|
||||||
|
assertEquals("Approved of added entry",
|
||||||
|
item1.isApproved(), storedItem.isApproved());
|
||||||
|
|
||||||
|
|
||||||
final String version2 = "2";
|
final String version2 = "2";
|
||||||
|
@ -115,6 +117,7 @@ public class DirectoryRosterStoreTest {
|
||||||
item2.addGroupName("examples");
|
item2.addGroupName("examples");
|
||||||
item2.setItemStatus(ItemStatus.subscribe);
|
item2.setItemStatus(ItemStatus.subscribe);
|
||||||
item2.setItemType(ItemType.none);
|
item2.setItemType(ItemType.none);
|
||||||
|
item2.setApproved(true);
|
||||||
store.addEntry(item2,version2);
|
store.addEntry(item2,version2);
|
||||||
assertEquals("Updating entry sets version correctly", version2, store.getRosterVersion());
|
assertEquals("Updating entry sets version correctly", version2, store.getRosterVersion());
|
||||||
storedItem = store.getEntry(userName);
|
storedItem = store.getEntry(userName);
|
||||||
|
@ -128,6 +131,8 @@ public class DirectoryRosterStoreTest {
|
||||||
item2.getItemType(), storedItem.getItemType());
|
item2.getItemType(), storedItem.getItemType());
|
||||||
assertEquals("ItemStatus of added entry",
|
assertEquals("ItemStatus of added entry",
|
||||||
item2.getItemStatus(), storedItem.getItemStatus());
|
item2.getItemStatus(), storedItem.getItemStatus());
|
||||||
|
assertEquals("Approved of added entry",
|
||||||
|
item2.isApproved(), storedItem.isApproved());
|
||||||
|
|
||||||
List<Item> entries = store.getEntries();
|
List<Item> entries = store.getEntries();
|
||||||
assertEquals("Number of entries", 1, entries.size());
|
assertEquals("Number of entries", 1, entries.size());
|
||||||
|
@ -143,6 +148,7 @@ public class DirectoryRosterStoreTest {
|
||||||
item4.addGroupName("Bar Friends");
|
item4.addGroupName("Bar Friends");
|
||||||
item4.setItemStatus(ItemStatus.subscribe);
|
item4.setItemStatus(ItemStatus.subscribe);
|
||||||
item4.setItemType(ItemType.both);
|
item4.setItemType(ItemType.both);
|
||||||
|
item4.setApproved(true);
|
||||||
|
|
||||||
ArrayList<Item> items34 = new ArrayList<RosterPacket.Item>();
|
ArrayList<Item> items34 = new ArrayList<RosterPacket.Item>();
|
||||||
items34.add(item3);
|
items34.add(item3);
|
||||||
|
@ -162,6 +168,8 @@ public class DirectoryRosterStoreTest {
|
||||||
item3.getItemType(), storedItem.getItemType());
|
item3.getItemType(), storedItem.getItemType());
|
||||||
assertEquals("ItemStatus of added entry",
|
assertEquals("ItemStatus of added entry",
|
||||||
item3.getItemStatus(), storedItem.getItemStatus());
|
item3.getItemStatus(), storedItem.getItemStatus());
|
||||||
|
assertEquals("Approved of added entry",
|
||||||
|
item3.isApproved(), storedItem.isApproved());
|
||||||
|
|
||||||
|
|
||||||
storedItem = store.getEntry(JidTestUtil.BARE_JID_2);
|
storedItem = store.getEntry(JidTestUtil.BARE_JID_2);
|
||||||
|
@ -175,6 +183,8 @@ public class DirectoryRosterStoreTest {
|
||||||
item4.getItemType(), storedItem.getItemType());
|
item4.getItemType(), storedItem.getItemType());
|
||||||
assertEquals("ItemStatus of added entry",
|
assertEquals("ItemStatus of added entry",
|
||||||
item4.getItemStatus(), storedItem.getItemStatus());
|
item4.getItemStatus(), storedItem.getItemStatus());
|
||||||
|
assertEquals("Approved of added entry",
|
||||||
|
item4.isApproved(), storedItem.isApproved());
|
||||||
|
|
||||||
entries = store.getEntries();
|
entries = store.getEntries();
|
||||||
assertEquals("Number of entries", 2, entries.size());
|
assertEquals("Number of entries", 2, entries.size());
|
||||||
|
|
Loading…
Reference in a new issue