class ZMSImpl implements Authorizer, KeyStore, ZMSHandler {

    @Override
    public void putGroupMembership(ResourceContext ctx, String domainName, String groupName, String memberName, String auditRef, GroupMembership membership) {

        final String caller = ctx.getApiName();
        logPrincipal(ctx);

        if (readOnlyMode) {
            throw ZMSUtils.requestError(SERVER_READ_ONLY_MESSAGE, caller);
        }

        validateRequest(ctx.request(), caller);

        validate(domainName, TYPE_DOMAIN_NAME, caller);
        setRequestDomain(ctx, domainName);
        validate(groupName, TYPE_ENTITY_NAME, caller);
        validate(memberName, TYPE_MEMBER_NAME, caller);
        validate(membership, TYPE_GROUP_MEMBERSHIP, caller);

        // for consistent handling of all requests, we're going to convert
        // all incoming object values into lower case (e.g. domain, role,
        // policy, service, etc name)

        domainName = domainName.toLowerCase();
        groupName = groupName.toLowerCase();
        memberName = memberName.toLowerCase();
        AthenzObject.GROUP_MEMBERSHIP.convertToLowerCase(membership);

        final Principal principal = ((RsrcCtxWrapper) ctx).principal();

        // verify that request is properly authenticated for this request

        verifyAuthorizedServiceGroupOperation(principal.getAuthorizedService(), caller, groupName);

        // verify that the member name in the URI and object provided match

        if (!memberName.equals(membership.getMemberName())) {
            throw ZMSUtils.requestError("putGroupMembership: Member name in URI and GroupMembership object do not match", caller);
        }

        // group name is optional so we'll verify only if the value is present in the object

        if (membership.getGroupName() != null && !groupName.equals(membership.getGroupName())) {
            throw ZMSUtils.requestError("putGroupMembership: Group name in URI and GroupMembership object do not match", caller);
        }

        // extract our role object to get its attributes

        AthenzDomain domain = getAthenzDomain(domainName, false);
        Group group = getGroupFromDomain(groupName, domain);

        if (group == null) {
            throw ZMSUtils.requestError("Invalid groupname specified", caller);
        }

        // create and normalize the role member object

        GroupMember groupMember = new GroupMember();
        groupMember.setMemberName(normalizeDomainAliasUser(memberName));
        groupMember.setPrincipalType(principalType(groupMember.getMemberName()));
        setGroupMemberExpiration(group, groupMember, caller);

        // check to see if we need to validate the principal

        final String userAuthorityFilter = enforcedUserAuthorityFilter(group.getUserAuthorityFilter(),
                domain.getDomain().getUserAuthorityFilter());
        validateGroupMemberPrincipal(groupMember.getMemberName(), groupMember.getPrincipalType(), userAuthorityFilter, caller);

        // authorization check which also automatically updates
        // the active and approved flags for the request

        if (!isAllowedPutGroupMembership(principal, domain, group, groupMember)) {
            throw ZMSUtils.forbiddenError("putGroupMembership: principal is not authorized to add members", caller);
        }

        // add the member to the specified role

        dbService.executePutGroupMembership(ctx, domainName, group, groupMember, auditRef);

        // new group member with pending status. Notify approvers

        if (groupMember.getApproved() == Boolean.FALSE) {
            sendGroupMembershipApprovalNotification(domainName, domain.getDomain().getOrg(), groupName,
                    groupMember.getMemberName(), auditRef, principal.getFullName(), group);
        }
    }

    public  ZMSImpl();

}