示例#1
0
        private static IList <NavBE> FilterDisallowedNavRecords(IList <NavBE> navPages)
        {
            Dictionary <ulong, NavBE> pagesToCheck = new Dictionary <ulong, NavBE>();
            List <NavBE> allowedPages    = new List <NavBE>(navPages.Count);
            Permissions  userPermissions = PermissionsBL.GetUserPermissions(DekiContext.Current.User);

            foreach (NavBE np in navPages)
            {
                ulong effectivePageRights = PermissionsBL.CalculateEffectivePageRights(new PermissionStruct((ulong)userPermissions, np.RestrictionFlags ?? ulong.MaxValue, 0));
                if (!PermissionsBL.IsActionAllowed(effectivePageRights, false, Permissions.BROWSE))
                {
                    pagesToCheck.Add(np.Id, np);
                }
                else
                {
                    allowedPages.Add(np);
                }
            }
            if (pagesToCheck.Count > 0)
            {
                IEnumerable <ulong> filteredOutPages;
                var allowedIds = PermissionsBL.FilterDisallowed(DekiContext.Current.User, pagesToCheck.Keys.ToArray(), false, out filteredOutPages, Permissions.BROWSE);
                foreach (var allowedId in allowedIds)
                {
                    allowedPages.Add(pagesToCheck[allowedId]);
                }
                return(allowedPages);
            }

            // No changes made..
            return(navPages);
        }
示例#2
0
        public static XDoc BuildXmlSiteMap(PageBE rootPage, string language)
        {
            Dictionary <ulong, PageBE> pagesById = null;

            rootPage = PageBL.PopulateDescendants(rootPage, null, out pagesById, ConfigBL.GetInstanceSettingsValueAs <int>(ConfigBL.MAX_SITEMAP_SIZE_KEY, ConfigBL.MAX_SITEMAP_SIZE));

            PageBE[] allowedPages = PermissionsBL.FilterDisallowed(DekiContext.Current.User, new List <PageBE>(pagesById.Values).ToArray(), false, new Permissions[] { Permissions.BROWSE });
            Dictionary <ulong, PageBE> allowedPagesById = allowedPages.AsHash(e => e.ID);
            Dictionary <ulong, PageBE> addedPagesById   = null;

            if (!string.IsNullOrEmpty(language))
            {
                List <ulong> pagesToRemove = new List <ulong>();
                foreach (KeyValuePair <ulong, PageBE> page in allowedPagesById)
                {
                    if (!string.IsNullOrEmpty(page.Value.Language) && !StringUtil.EqualsInvariantIgnoreCase(page.Value.Language, language))
                    {
                        pagesToRemove.Add(page.Key);
                    }
                }
                foreach (ulong pageId in pagesToRemove)
                {
                    allowedPagesById.Remove(pageId);
                }
            }
            PageBL.AddParentsOfAllowedChildren(rootPage, allowedPagesById, addedPagesById);

            return(BuildXmlSiteMap(rootPage, new XDoc("pages"), allowedPagesById));
        }
        private RoleBE GetRoleFromUrl(bool mustExist)
        {
            RoleBE r;
            string roleid = DreamContext.Current.GetParam("roleid");

            // Double decoding of name is done to work around a mod_proxy issue that strips out slashes
            roleid = XUri.Decode(roleid);
            if (roleid.StartsWith("="))
            {
                string name = roleid.Substring(1);
                r = PermissionsBL.GetRoleByName(name);
                if (r == null && mustExist)
                {
                    throw new SiteRoleNameNotFoundException(name);
                }
            }
            else
            {
                uint roleIdInt;
                if (!uint.TryParse(roleid, out roleIdInt))
                {
                    throw new SiteRoleIdInvalidArgumentException();
                }
                r = PermissionsBL.GetRoleById(roleIdInt);
                if (r == null && mustExist)
                {
                    throw new SiteRoleIdNotFoundException(roleIdInt);
                }
            }
            return(r);
        }
        public Yield GetGroupUsers(DreamContext context, DreamMessage request, Result <DreamMessage> response)
        {
            PermissionsBL.CheckUserAllowed(DekiContext.Current.User, Permissions.READ);
            GroupBE      group       = GetGroupFromUrl();
            DreamMessage responseMsg = null;

            uint totalCount;
            uint queryCount;
            var  usersInGroup = UserBL.GetUsersByQuery(context, group.Id, out totalCount, out queryCount);

            XDoc ret = new XDoc("users");

            ret.Attr("count", usersInGroup.Count());
            ret.Attr("querycount", queryCount);
            ret.Attr("totalcount", totalCount);
            ret.Attr("href", DekiContext.Current.ApiUri.At("groups", group.Id.ToString(), "users"));

            foreach (UserBE member in usersInGroup)
            {
                ret.Add(UserBL.GetUserXml(member, null, Utils.ShowPrivateUserInfo(member)));
            }

            responseMsg = DreamMessage.Ok(ret);

            response.Return(responseMsg);
            yield break;
        }
        public Yield ProxyToService(DreamContext context, DreamMessage request, Result <DreamMessage> response)
        {
            PermissionsBL.IsUserAllowed(DekiContext.Current.User, Permissions.ADMIN);

            //Private feature requires api-key
            var identifier = context.GetParam("id");

            ServiceRepository.IServiceInfo serviceInfo = null;
            if (identifier.StartsWith("="))
            {
                serviceInfo = DekiContext.Current.Instance.RunningServices[XUri.Decode(identifier.Substring(1))];
            }
            else
            {
                uint serviceId;
                if (uint.TryParse(identifier, out serviceId))
                {
                    serviceInfo = DekiContext.Current.Instance.RunningServices[serviceId];
                }
                else
                {
                    throw new DreamBadRequestException(string.Format("Invalid id '{0}'", identifier));
                }
            }
            if (serviceInfo == null)
            {
                throw new ServiceNotFoundException(identifier);
            }
            var proxyUri = serviceInfo.ServiceUri.At(context.GetSuffixes(UriPathFormat.Original).Skip(1).ToArray());

            yield return(context.Relay(Plug.New(proxyUri), request, response));
        }
        public Yield GetServiceById(DreamContext context, DreamMessage request, Result <DreamMessage> response)
        {
            bool privateDetails = PermissionsBL.IsUserAllowed(DekiContext.Current.User, Permissions.ADMIN);

            //Private feature requires api-key
            var  identifier = context.GetParam("id");
            uint serviceId  = 0;

            if (identifier.StartsWith("="))
            {
                var serviceInfo = DekiContext.Current.Instance.RunningServices[XUri.Decode(identifier.Substring(1))];
                if (serviceInfo != null)
                {
                    serviceId = serviceInfo.ServiceId;
                }
            }
            else
            {
                if (!uint.TryParse(identifier, out serviceId))
                {
                    throw new DreamBadRequestException(string.Format("Invalid id '{0}'", identifier));
                }
            }
            ServiceBE service = ServiceBL.GetServiceById(serviceId);

            if (service == null)
            {
                throw new ServiceNotFoundException(identifier);
            }
            response.Return(DreamMessage.Ok(ServiceBL.GetServiceXmlVerbose(DekiContext.Current.Instance, service, null, privateDetails)));
            yield break;
        }
示例#7
0
        public Yield PostArchiveFilesRestore(DreamContext context, DreamMessage request, Result <DreamMessage> response)
        {
            PermissionsBL.CheckUserAllowed(DekiContext.Current.User, Permissions.ADMIN);

            // parameter parsing
            PageBE destPage = null;
            string to       = context.GetParam("to", string.Empty);

            if (to != string.Empty)
            {
                destPage = PageBL_GetPageFromPathSegment(false, to);
            }
            PageBE     parentPage;
            ResourceBE removedFile = GetAttachment(context, request, Permissions.NONE, true, true, out parentPage);

            if (!removedFile.ResourceIsDeleted)
            {
                throw new AttachmentArchiveFileNotDeletedNotFoundException();
            }

            //Optionally move the restored file to the given page
            if (null == destPage)
            {
                destPage = parentPage;
            }
            AttachmentBL.Instance.RestoreAttachment(removedFile, destPage, DateTime.UtcNow, 0);
            response.Return(DreamMessage.Ok());
            yield break;
        }
示例#8
0
        public Yield PostFileMove(DreamContext context, DreamMessage request, Result <DreamMessage> response)
        {
            PageBE     sourcePage = null;
            PageBE     destPage   = null;
            ResourceBE fileToMove = GetAttachmentFromUrl(context, true, out sourcePage, false, false);

            // parameter parsing
            string name = context.GetParam("name", null);
            string to   = context.GetParam("to", null);

            if (string.IsNullOrEmpty(name) && string.IsNullOrEmpty(to))
            {
                throw new AttachmentMoveInvalidArgumentException();
            }
            if (name == null)
            {
                name = fileToMove.Name;
            }
            destPage = to != null?PageBL_GetPageFromPathSegment(true, to) : sourcePage;

            //Check if we're actually doing anything
            if (sourcePage.ID == destPage.ID && fileToMove.Name.EqualsInvariant(name))
            {
                throw new AttachmentNotChangedInvalidOperationException(fileToMove.Name, destPage.Title.AsUserFriendlyName());
            }

            //Ensure write access to source and destination pages.
            IList <PageBE> pList = PermissionsBL.FilterDisallowed(DekiContext.Current.User, new PageBE[] { sourcePage, destPage }, true, Permissions.UPDATE);

            // perform the move
            ResourceBE ret = AttachmentBL.Instance.MoveAttachment(fileToMove, sourcePage, destPage, name, true);

            response.Return(DreamMessage.Ok(AttachmentBL.Instance.GetFileXml(ret, true, null, false)));
            yield break;
        }
示例#9
0
        private static IList <CommentBE> ApplyPermissionFilter(IList <CommentBE> comments)
        {
            var ret = new List <CommentBE>();

            //Collect page ids
            var pageids = comments.Select(e => e.PageId);

            //Determine pages to filter out due to perms
            IEnumerable <ulong> filteredOutPages;

            PermissionsBL.FilterDisallowed(DekiContext.Current.User, pageids, false, out filteredOutPages, Permissions.READ);

            //Hash filtered out pageids
            var filteredOutPagesHash = new Dictionary <ulong, object>();

            foreach (ulong f in filteredOutPages)
            {
                filteredOutPagesHash[f] = null;
            }

            //Remove filtered out comments
            foreach (CommentBE c in comments)
            {
                if (!filteredOutPagesHash.ContainsKey(c.PageId))
                {
                    ret.Add(c);
                }
            }

            return(ret);
        }
示例#10
0
        public Yield PostUserAuth(DreamContext context, DreamMessage request, Result <DreamMessage> response)
        {
            uint serviceId = context.GetParam <uint>("authprovider", 0);
            bool altPassword;

            //This will internally fail with a 501 response if credentials are invalid.
            //Anonymous accounts (no credentials/authtoken) are not allowed -> 401
            UserBE u = SetContextAndAuthenticate(request, serviceId, context.Verb == Verb.POST, false, true, out altPassword);

            PermissionsBL.CheckUserAllowed(u, Permissions.LOGIN);


            string token = AuthBL.CreateAuthTokenForUser(u);

            try {
                PageBL.CreateUserHomePage(DekiContext.Current.User);
            } catch { }
            XUri         redirectUri = XUri.TryParse(context.GetParam("redirect", null));
            DreamMessage ret         = BuildSetAuthTokenResponse(token, redirectUri);

            DekiContext.Current.Instance.EventSink.UserLogin(DekiContext.Current.Now, DekiContext.Current.User);

            //TODO Max: Set a response header or status to indicate that an alt password was used.
            response.Return(ret);
            yield break;
        }
示例#11
0
        public Yield GetUsers(DreamContext context, DreamMessage request, Result <DreamMessage> response)
        {
            // TODO (steveb): add 'emailfilter' and use it to obsolete 'usernameemailfilter'; 'usernamefilter', 'fullnamefilter', and 'emailfilter'
            //                should be OR'ed together when they are present.

            PermissionsBL.CheckUserAllowed(DekiContext.Current.User, Permissions.READ);
            uint totalCount;
            uint queryCount;
            var  users  = UserBL.GetUsersByQuery(context, null, out totalCount, out queryCount);
            XDoc result = new XDoc("users");

            result.Attr("count", users.Count());
            result.Attr("querycount", queryCount);
            result.Attr("totalcount", totalCount);
            result.Attr("href", DekiContext.Current.ApiUri.At("users"));
            bool verbose = context.GetParam <bool>("verbose", true);

            foreach (UserBE u in users)
            {
                if (verbose)
                {
                    result.Add(UserBL.GetUserXmlVerbose(u, null, Utils.ShowPrivateUserInfo(u), true, true));
                }
                else
                {
                    result.Add(UserBL.GetUserXml(u, null, Utils.ShowPrivateUserInfo(u)));
                }
            }
            response.Return(DreamMessage.Ok(result));
            yield break;
        }
示例#12
0
        private static XDoc AppendBanXml(XDoc doc, BanBE ban)
        {
            UserBE createdBy = UserBL.GetUserById(ban.ByUserId);

            doc.Attr("id", ban.Id);
            doc.Attr("href", DekiContext.Current.ApiUri.At("site", "bans", ban.Id.ToString()));
            if (createdBy != null)
            {
                doc.Add(UserBL.GetUserXml(createdBy, "createdby", Utils.ShowPrivateUserInfo(createdBy)));
            }
            doc.Elem("date.modified", ban.LastEdit);
            doc.Elem("description", ban.Reason);
            doc.Elem("date.expires", ban.Expires);
            doc.Add(PermissionsBL.GetPermissionXml(ban.RevokeMask, "revoked"));
            doc.Start("ban.addresses");
            if (ban.BanAddresses != null)
            {
                foreach (string address in ban.BanAddresses)
                {
                    doc.Elem("address", address);
                }
            }
            doc.End();
            doc.Start("ban.users");
            if (ban.BanUserIds != null)
            {
                var banUsers = DbUtils.CurrentSession.Users_GetByIds(ban.BanUserIds);
                foreach (UserBE u in banUsers)
                {
                    doc.Add(UserBL.GetUserXml(u, null, Utils.ShowPrivateUserInfo(createdBy)));
                }
            }
            doc.End();
            return(doc);
        }
示例#13
0
        private static BanBE ReadBanXml(XDoc doc)
        {
            BanBE b = new BanBE();

            b.BanAddresses = new List <string>();
            b.BanUserIds   = new List <uint>();
            try {
                b.Reason     = doc["description"].AsText;
                b.RevokeMask = PermissionsBL.MaskFromPermissionList(PermissionsBL.PermissionListFromString(doc["permissions.revoked/operations"].AsText ?? string.Empty));
                b.LastEdit   = DateTime.UtcNow;
                b.Expires    = doc["date.expires"].AsDate;
                b.ByUserId   = DekiContext.Current.User.ID;
                foreach (XDoc val in doc["ban.addresses/address"])
                {
                    if (!val.IsEmpty)
                    {
                        b.BanAddresses.Add(val.AsText);
                    }
                }
                foreach (XDoc val in doc["ban.users/user"])
                {
                    uint?id = val["@id"].AsUInt;
                    if (id != null)
                    {
                        b.BanUserIds.Add(id ?? 0);
                    }
                }
            } catch (ResourcedMindTouchException) {
                throw;
            } catch (Exception x) {
                throw new MindTouchInvalidOperationException(x.Message, x);
            }
            return(b);
        }
示例#14
0
        public Yield GetFiles(DreamContext context, DreamMessage request, Result <DreamMessage> response)
        {
            PermissionsBL.CheckUserAllowed(DekiContext.Current.User, Permissions.READ);
            uint   skip        = context.GetParam <uint>("skip", 0);
            uint   numfiles    = 100;
            string numfilesStr = context.GetParam("numfiles", numfiles.ToString());

            if (StringUtil.EqualsInvariantIgnoreCase(numfilesStr, "ALL"))
            {
                numfiles = uint.MaxValue;
            }
            else
            {
                if (!uint.TryParse(numfilesStr, out numfiles))
                {
                    throw new AttachmentCannotParseNumFilesInvalidArgumentException();
                }
            }

            IList <ResourceBE> files = AttachmentBL.Instance.RetrieveAttachments(skip, numfiles);
            XDoc ret = AttachmentBL.Instance.GetFileXml(files, false, null, null, null);

            response.Return(DreamMessage.Ok(ret));
            yield break;
        }
示例#15
0
        private IList <PageBE> GetTaggedPages(TagBE tag)
        {
            IList <ulong>  pageIds = _session.Tags_GetPageIds(tag.Id);
            IList <PageBE> pages   = PageBL.GetPagesByIdsPreserveOrder(pageIds);

            return(PermissionsBL.FilterDisallowed(_user, pages, false, Permissions.BROWSE));
        }
示例#16
0
        public static XDoc GetGroupXmlVerbose(GroupBE group, string relation)
        {
            XDoc groupXml = GetGroupXml(group, relation);

            ServiceBE authService = ServiceBL.GetServiceById(group.ServiceId);

            if (authService != null)
            {
                groupXml.Add(ServiceBL.GetServiceXml(authService, "authentication"));
            }

            groupXml.Start("users");
            if (group.UserIdsList != null)
            {
                groupXml.Attr("count", group.UserIdsList.Length);
            }

            groupXml.Attr("href", DekiContext.Current.ApiUri.At("groups", group.Id.ToString(), "users"));
            groupXml.End();

            //Permissions for the group
            RoleBE role = PermissionsBL.GetRoleById(group.RoleId);

            groupXml.Add(PermissionsBL.GetRoleXml(role, "group"));
            return(groupXml);
        }
示例#17
0
        public Yield GetArchivePageSubpages(DreamContext context, DreamMessage request, Result <DreamMessage> response)
        {
            PermissionsBL.CheckUserAllowed(DekiContext.Current.User, Permissions.ADMIN);
            XDoc responseXml = PageArchiveBL.GetArchivedSubPagesXml(context.GetParam <uint>("pageid"));

            response.Return(DreamMessage.Ok(responseXml));
            yield break;
        }
        public Yield GetSiteRole(DreamContext context, DreamMessage request, Result <DreamMessage> response)
        {
            RoleBE role = GetRoleFromUrl();
            XDoc   ret  = PermissionsBL.GetRoleXml(role, null);

            response.Return(DreamMessage.Ok(ret));
            yield break;
        }
示例#19
0
        public Yield GetArchivePageContents(DreamContext context, DreamMessage request, Result <DreamMessage> response)
        {
            PermissionsBL.CheckUserAllowed(DekiContext.Current.User, Permissions.ADMIN);
            DreamMessage ret = PageArchiveBL.BuildDeletedPageContents(context.GetParam <uint>("pageid"));

            response.Return(ret);
            yield break;
        }
示例#20
0
        public Yield GetBan(DreamContext context, DreamMessage request, Result <DreamMessage> response)
        {
            PermissionsBL.CheckUserAllowed(DekiContext.Current.User, Permissions.ADMIN);
            BanBE ban = GetBanFromRequest(context, context.GetParam <uint>("banid"));

            response.Return(DreamMessage.Ok(BanningBL.GetBanXml(ban)));
            yield break;
        }
示例#21
0
        public Yield GetSiteStatus(DreamContext context, DreamMessage request, Result <DreamMessage> response)
        {
            PermissionsBL.CheckUserAllowed(DekiContext.Current.User, Permissions.UPDATE);
            var status = new XDoc("status")
                         .Elem("state", DekiContext.Current.Instance.Status);

            response.Return(DreamMessage.Ok(status));
            yield break;
        }
示例#22
0
        public Yield PostBans(DreamContext context, DreamMessage request, Result <DreamMessage> response)
        {
            PermissionsBL.CheckUserAllowed(DekiContext.Current.User, Permissions.ADMIN);
            BanBE ban = BanningBL.SaveBan(request.ToDocument());

            DekiContext.Current.Instance.EventSink.BanCreated(DekiContext.Current.Now, ban);
            response.Return(DreamMessage.Ok(BanningBL.GetBanXml(ban)));
            yield break;
        }
        public Yield GetSiteOperations(DreamContext context, DreamMessage request, Result <DreamMessage> response)
        {
            XDoc ret = new XDoc("operations");

            ret.Attr("href", DekiContext.Current.ApiUri.At("site", "operations"));
            ret.Value(string.Join(",", PermissionsBL.PermissionsToArray(ulong.MaxValue)));
            response.Return(DreamMessage.Ok(ret));
            yield break;
        }
示例#24
0
        public Yield GetGroup(DreamContext context, DreamMessage request, Result <DreamMessage> response)
        {
            PermissionsBL.CheckUserAllowed(DekiContext.Current.User, Permissions.READ);
            GroupBE      group       = GetGroupFromUrl();
            DreamMessage responseMsg = DreamMessage.Ok(GroupBL.GetGroupXmlVerbose(group, null));

            response.Return(responseMsg);
            yield break;
        }
示例#25
0
        public Yield PostGroupUsers(DreamContext context, DreamMessage request, Result <DreamMessage> response)
        {
            PermissionsBL.CheckUserAllowed(DekiContext.Current.User, Permissions.ADMIN);
            GroupBE group = GetGroupFromUrl();

            group = GroupBL.AddGroupMembers(group, request.ToDocument());
            response.Return(DreamMessage.Ok(GroupBL.GetGroupXmlVerbose(group, null)));
            yield break;
        }
示例#26
0
        private static void ParseGroupXml(XDoc groupDoc, out uint?id, out string name, out ServiceBE authService, out RoleBE role, out UserBE[] userList)
        {
            name = groupDoc["groupname"].AsText ?? groupDoc["name"].AsText;
            string authserviceidstr = groupDoc["service.authentication/@id"].AsText;
            string rolestr          = groupDoc["permissions.group/role"].AsText;

            authService = null;
            role        = null;
            id          = null;


            if (!groupDoc["@id"].IsEmpty)
            {
                uint id_temp;
                if (!uint.TryParse(groupDoc["@id"].Contents, out id_temp))
                {
                    throw new GroupIdAttributeInvalidArgumentException();
                }
                id = id_temp;
            }

            if (!string.IsNullOrEmpty(authserviceidstr))
            {
                uint serviceid;
                if (!uint.TryParse(authserviceidstr, out serviceid))
                {
                    throw new ServiceAuthIdAttrInvalidArgumentException();
                }

                authService = ServiceBL.GetServiceById(serviceid);
                if (authService == null)
                {
                    throw new ServiceDoesNotExistInvalidArgumentException(serviceid);
                }
            }

            if (!string.IsNullOrEmpty(rolestr))
            {
                role = PermissionsBL.GetRoleByName(rolestr);
                if (role == null)
                {
                    throw new RoleDoesNotExistInvalidArgumentException(rolestr);
                }
            }
            else
            {
                role = PermissionsBL.RetrieveDefaultRoleForNewAccounts();
            }
            if (!groupDoc["users"].IsEmpty)
            {
                userList = ReadUserListXml(groupDoc["users"]);
            }
            else
            {
                userList = new UserBE[] { }
            };
        }
示例#27
0
        public Yield DeleteGroup(DreamContext context, DreamMessage request, Result <DreamMessage> response)
        {
            PermissionsBL.CheckUserAllowed(DekiContext.Current.User, Permissions.ADMIN);
            GroupBE group = GetGroupFromUrl();

            DbUtils.CurrentSession.Groups_Delete(group.Id);
            response.Return(DreamMessage.Ok());
            yield break;
        }
示例#28
0
        public Yield GetArchiveFiles(DreamContext context, DreamMessage request, Result <DreamMessage> response)
        {
            PermissionsBL.CheckUserAllowed(DekiContext.Current.User, Permissions.ADMIN);
            IList <ResourceBE> removedFiles = AttachmentBL.Instance.GetDeletedAttachments(null, null);
            XDoc responseXml = AttachmentBL.Instance.GetFileXml(removedFiles, true, "archive", null, null);

            response.Return(DreamMessage.Ok(responseXml));
            yield break;
        }
示例#29
0
        public Yield GetArchive(DreamContext context, DreamMessage request, Result <DreamMessage> response)
        {
            PermissionsBL.CheckUserAllowed(DekiContext.Current.User, Permissions.ADMIN);
            XDoc ret = new XDoc("archive");

            ret.Start("pages.archive").Attr("href", DekiContext.Current.ApiUri.At("archive", "pages")).End();
            ret.Start("files.archive").Attr("href", DekiContext.Current.ApiUri.At("archive", "files")).End();
            response.Return(DreamMessage.Ok(ret));
            yield break;
        }
示例#30
0
        public Yield DeleteGroupUser(DreamContext context, DreamMessage request, Result <DreamMessage> response)
        {
            PermissionsBL.CheckUserAllowed(DekiContext.Current.User, Permissions.ADMIN);
            GroupBE group = GetGroupFromUrl();
            UserBE  user  = GetUserFromUrlMustExist();

            group = GroupBL.RemoveGroupMember(group, user);
            response.Return(DreamMessage.Ok(GroupBL.GetGroupXmlVerbose(group, null)));
            yield break;
        }