GroupOSCORERootGroupMembershipResource.java
/*******************************************************************************
* Copyright (c) 2025, RISE AB
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************/
package se.sics.ace.oscore.rs.oscoreGroupManager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.californium.core.CoapResource;
import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.coap.Request;
import org.eclipse.californium.core.coap.Response;
import org.eclipse.californium.core.server.resources.CoapExchange;
import com.upokecenter.cbor.CBORObject;
import com.upokecenter.cbor.CBORType;
import se.sics.ace.AceException;
import se.sics.ace.Constants;
import se.sics.ace.GroupcommParameters;
import se.sics.ace.Util;
import se.sics.ace.coap.CoapReq;
import se.sics.ace.oscore.GroupInfo;
/**
* Definition of the root group-membership resource for Group OSCORE
*
* Children of this resource are the group-membership resources,
* whose implementation is provided by the class GroupOSCOREGroupMembershipResource
*/
public class GroupOSCORERootGroupMembershipResource extends CoapResource {
private Map<String, GroupInfo> existingGroupInfo = new HashMap<>();
/**
* Constructor
* @param resId the resource identifier
* @param existingGroupInfo the set of information of the existing OSCORE groups
*/
public GroupOSCORERootGroupMembershipResource(String resId, Map<String, GroupInfo> existingGroupInfo) {
// set resource identifier
super(resId);
// set display name
getAttributes().setTitle("Group OSCORE Group-Membership Resource " + resId);
this.existingGroupInfo = existingGroupInfo;
}
@Override
public void handleFETCH(CoapExchange exchange) {
System.out.println("FETCH request reached the GM");
String subject = null;
Request request = exchange.advanced().getCurrentRequest();
try {
subject = CoapReq.getInstance(request).getSenderId();
} catch (AceException e) {
System.err.println("Error while retrieving the client identity: " + e.getMessage());
}
if (subject == null) {
// At this point, this should not really happen, due to the earlier check at the Token Repository
exchange.respond(CoAP.ResponseCode.UNAUTHORIZED,
"Unauthenticated client tried to get access");
return;
}
byte[] requestPayload = exchange.getRequestPayload();
if(requestPayload == null) {
exchange.respond(CoAP.ResponseCode.BAD_REQUEST,
"A payload must be present");
return;
}
CBORObject requestCBOR = CBORObject.DecodeFromBytes(requestPayload);
// The payload of the request must be a CBOR Map
if (!requestCBOR.getType().equals(CBORType.Map)) {
exchange.respond(CoAP.ResponseCode.BAD_REQUEST,
"Invalid payload format");
return;
}
// The CBOR Map must include exactly one element, i.e. 'gid'
if ((requestCBOR.size() != 1) || (!requestCBOR.ContainsKey(GroupcommParameters.GID))) {
exchange.respond(CoAP.ResponseCode.BAD_REQUEST,
"Invalid payload format");
return;
}
// The 'gid' element must be a CBOR array, with at least one element
if (requestCBOR.get(GroupcommParameters.GID).getType() != CBORType.Array ||
requestCBOR.get(GroupcommParameters.GID).size() == 0) {
exchange.respond(CoAP.ResponseCode.BAD_REQUEST,
"Invalid payload format");
return;
}
// Each element of 'gid' element must be a CBOR byte string
for (int i = 0 ; i < requestCBOR.get(GroupcommParameters.GID).size(); i++) {
if (requestCBOR.get(GroupcommParameters.GID).get(i).getType() != CBORType.ByteString) {
exchange.respond(CoAP.ResponseCode.BAD_REQUEST,
"Invalid payload format");
return;
}
}
List<CBORObject> inputGroupIds = new ArrayList<CBORObject> ();
for (int i = 0; i < requestCBOR.get(GroupcommParameters.GID).size(); i++) {
inputGroupIds.add(requestCBOR.get(GroupcommParameters.GID).get(i));
}
List<String> preliminaryGroupNames = new ArrayList<String>();
List<CBORObject> finalGroupNames = new ArrayList<CBORObject>();
List<CBORObject> finalGroupIds = new ArrayList<CBORObject>();
List<CBORObject> finalGroupURIs = new ArrayList<CBORObject>();
// Navigate the list of existing OSCORE groups
for (String groupName : existingGroupInfo.keySet()) {
GroupInfo myGroup = existingGroupInfo.get(groupName);
byte[] storedGid = myGroup.getGroupId();
// Navigate the list of Group IDs specified in the request
for (int i = 0; i < inputGroupIds.size(); i ++) {
byte[] inputGid = inputGroupIds.get(i).GetByteString();
// A match is found with the examined OSCORE group
if (Arrays.equals(storedGid, inputGid)) {
// Store the used Group Name for future inspection
preliminaryGroupNames.add(groupName);
// No need to further consider this Group ID value
inputGroupIds.remove(i);
break;
}
}
if (inputGroupIds.isEmpty())
break;
}
// Selects only names of groups where the requesting client is
// a current member or is authorized to have any role about
for (String groupName : preliminaryGroupNames) {
GroupInfo targetedGroup = existingGroupInfo.get(groupName);
if (!targetedGroup.isGroupMember(subject)) {
// The requester is not a current group member.
//
// This is still fine, as long as at least one Access Token allows
// the requesting client to have any role with respect to the group
if (Util.getGroupOSCORERolesFromToken(subject, groupName) == null) {
// No Access Token allows the requesting client node to have
// to have any role with respect to the group
// Move to considering the next group
continue;
}
}
finalGroupNames.add(CBORObject.FromObject(groupName));
byte[] gid = targetedGroup.getGroupId();
finalGroupIds.add(CBORObject.FromObject(gid));
finalGroupURIs.add(CBORObject.FromObject(this.getURI() + "/" + groupName));
}
// Respond to the Group Name and URI Retrieval Request
byte[] responsePayload = null;
Response coapResponse = new Response(CoAP.ResponseCode.CONTENT);
// The response is an empty CBOR byte string
if (finalGroupNames.size() == 0) {
byte[] emptyArray = new byte[0];
responsePayload = CBORObject.FromObject(emptyArray).EncodeToBytes();
}
// The response is a CBOR may including three CBOR arrays
else {
CBORObject myResponse = CBORObject.NewMap();
CBORObject gnameArray = CBORObject.NewArray();
CBORObject gidArray = CBORObject.NewArray();
CBORObject guriArray = CBORObject.NewArray();
for (int i = 0; i < finalGroupNames.size(); i++) {
gnameArray.Add(finalGroupNames.get(i));
gidArray.Add(finalGroupIds.get(i));
guriArray.Add(finalGroupURIs.get(i));
}
myResponse.Add(GroupcommParameters.GID, gidArray);
myResponse.Add(GroupcommParameters.GNAME, gnameArray);
myResponse.Add(GroupcommParameters.GURI, guriArray);
responsePayload = myResponse.EncodeToBytes();
coapResponse.getOptions().setContentFormat(Constants.APPLICATION_ACE_GROUPCOMM_CBOR);
}
coapResponse.setPayload(responsePayload);
exchange.respond(coapResponse);
}
}