1 /* Copyright (C) 2000-2010 Thomas Bopp, Thorsten Hampel, Ludger Merkens
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 * $Id: GroupFactory.pike,v 1.7 2010/08/20 20:42:25 astra Exp $
19 inherit "/factories/ObjectFactory";
26 #include <attributes.h>
28 #include <exception.h>
29 //! This factory creates a group with group workarea.
30 class GroupFactory : public ObjectFactory{
42 sReservedNames = ({ "steam", "admin", "everyone", "privgroups" });
46 private array test_objects = ({ });
52 register_attribute(Attribute(GROUP_MEMBERSHIP_REQS, "request membership",
53 CMD_TYPE_ARRAY, ({ }), 0, CONTROL_ATTR_USER,
54 EVENT_ATTRIBUTES_QUERY, EVENT_ATTRIBUTES_CHANGE));
55 register_attribute(UserAttribute(GROUP_WORKROOM,"workroom",CMD_TYPE_OBJECT,0));
56 register_attribute(UserAttribute(GROUP_PUBLICROOM,"workroom",CMD_TYPE_OBJECT,0));
57 register_attribute(UserAttribute(GROUP_CALENDAR,"calendar",CMD_TYPE_OBJECT,0));
58 register_attribute(UserAttribute(GROUP_MAXSIZE,"Group Maximum Pending Size",
60 register_attribute(UserAttribute(GROUP_MSG_ACCEPT,"Group Accept Message",
67 * Create a new group with the name "name" and optionally a parent group. The
68 * name parameter needs to be without "." in it. The group will become a
69 * top-level group if no parent group is set. Otherwise it will become
70 * a member of the specified group. If the parent is called "Test" and
71 * this group is named "subgroup" the result would be "Test.subgroup".
73 * string name - the name for the new group(without parents)
74 * object parentgroup - the parent group object.
76 * @param mapping vars - the parameters within a mapping (see function docs)
77 * @return the created group object or 0
80 object execute(mapping vars)
82 object grp, parentgroup;
86 parentgroup = vars["parentgroup"];
87 // check if the parent group can be used
89 if ( search(name, ".") >= 0 )
90 steam_error("Using '.' in group names is forbidden !");
92 if ( objectp(parentgroup) ) {
93 _SECURITY->check_access(
94 parentgroup, CALLER, SANCTION_INSERT, ROLE_INSERT_ALL ,false);
95 name = parentgroup->get_identifier() + "." + name;
98 _SECURITY->check_access(
100 ROLE_CREATE_TOP_GROUPS,
103 object ogrp = MODULE_GROUPS->lookup(name);
104 if ( objectp(ogrp) ) {
105 if ( search(sReservedNames, lower_case(name)) >= 0 )
106 THROW("The name " + name + " is reserved for system groups.",
108 THROW("Group with that name ("+name+") already exists!", E_ACCESS);
110 ogrp = MODULE_USERS->lookup(name);
112 steam_error("There is already a user named '"+name+"' !");
114 try_event(EVENT_EXECUTE, CALLER, grp);
115 grp = object_create(name, CLASS_NAME_GROUP, 0,
117 vars["attributesAcquired"],
118 vars["attributesLocked"],
120 vars["sanctionMeta"]);
122 function grp_set_attribute = grp->get_function("do_set_attribute");
123 function grp_lock_attribute = grp->get_function("do_lock_attribute");
124 function grp_sanction_object = grp->get_function("do_sanction_object");
126 grp->set_group_name(name);
127 grp_set_attribute(OBJ_NAME, vars->name);
128 grp_lock_attribute(OBJ_NAME);
130 if ( objectp(parentgroup) ) {
131 grp->set_parent(parentgroup);
133 if ( parentAddMember != 1 ) {
137 object workroom, factory;
139 factory = _Server->get_factory(CLASS_ROOM);
141 workroom = factory->execute(([
142 "name":vars->name+"'s workarea",
144 grp_set_attribute(GROUP_WORKROOM, workroom);
145 grp_lock_attribute(GROUP_WORKROOM);
147 object steam = GROUP("steam");
148 if ( objectp(steam) )
149 grp_sanction_object(steam, SANCTION_READ); // make readable
152 object calendar=_Server->get_factory(CLASS_CALENDAR)->execute(([
153 "name":name+"'s calendar",
154 "attributesLocked": ([ CALENDAR_OWNER: 1, ]),
156 grp_set_attribute(GROUP_CALENDAR, calendar);
157 grp_lock_attribute(GROUP_CALENDAR);
159 if ( mappingp(vars["exits"]) )
160 grp_set_attribute(GROUP_EXITS, vars["exits"]);
162 grp_set_attribute(GROUP_EXITS, ([ workroom:
163 workroom->get_identifier(), ]));
164 run_event(EVENT_EXECUTE, CALLER, grp);
168 object find_parent(object group)
170 if ( objectp(group) )
171 return group->get_parent();
178 * Move a group to a new parent group. Everything is updated accordingly.
180 * @param object group - the group to move
181 * @param object new_parent - the new parent group
182 * @return true or false
184 bool move_group(object group, object new_parent)
186 if ( !objectp(group) )
187 steam_error("move_group() needs a group object to move!");
188 if ( !objectp(new_parent) )
189 steam_error("move_group() needs a target for moving the group!");
191 _SECURITY->check_access(group,CALLER,SANCTION_WRITE,ROLE_WRITE_ALL,false);
193 string identifier = get_group_name(group);
194 foreach(new_parent->get_members(), object grp) {
195 if ( objectp(grp) && grp->get_object_class() & CLASS_GROUP )
196 if ( grp != group && get_group_name(grp) == identifier )
197 steam_error("Naming conflict for group: already found group "+
198 "with same name in target!");
200 object parent = group->get_parent();
201 object groups = get_module("groups");
202 if ( !objectp(parent) ) {
203 // try to find some parent anyhow
204 parent = find_parent(group);
207 // unmount group from home module:
208 int is_mounted = get_module( "home" )->is_mounted( group );
209 if ( is_mounted ) get_module( "home" )->unmount( group );
211 if ( objectp(parent) && parent != new_parent ) {
212 //werror("- found parent group: " + parent->get_identifier() + "\n");
213 // check for permissions required
214 parent->remove_member(group);
216 if ( !new_parent->is_member(group) )
217 new_parent->add_member(group);
219 string new_name = new_parent->get_identifier()+"."+get_group_name(group);
220 groups->rename_group(group, new_name);
221 group->set_group_name(new_name);
223 // re-mount group in home module:
224 if ( is_mounted ) get_module( "home" )->mount( group );
226 // now we have to rename all subgroups:
227 foreach(group->get_sub_groups(), object subgroup) {
228 if ( objectp(subgroup) && subgroup->status() > 0 ) {
229 move_group(subgroup, group); // this is not actually a move, but should update name
232 object workroom = group->query_attribute(GROUP_WORKROOM);
233 workroom->update_path();
237 string get_group_name(object group)
239 string identifier = group->get_identifier();
240 array gn = (identifier / ".");
241 if ( sizeof(gn) == 0 )
243 return gn[sizeof(gn)-1];
246 bool rename_group(object group, string new_name)
248 _SECURITY->check_access(group,CALLER,SANCTION_WRITE,ROLE_WRITE_ALL,false);
249 if ( search( new_name, "." ) >= 0 ) return false;
250 object groups = get_module("groups");
252 object parent = find_parent(group);
253 string raw = new_name;
254 if ( objectp(parent) )
255 new_name = parent->get_identifier() + "." + new_name;
257 if ( new_name == group->get_group_name() )
260 // check whether a group with the target name already exists:
261 object old_group = groups->lookup( new_name );
262 if ( objectp(old_group) && old_group != group )
265 _Persistence->uncache_object( group );
267 string old_name = group->get_group_name();
268 // unmount group from home module:
269 int is_mounted = get_module( "home" )->is_mounted( group );
270 if ( is_mounted ) get_module( "home" )->unmount( group );
272 groups->rename_group(group, new_name);
273 group->set_group_name(new_name);
274 group->unlock_attribute(OBJ_NAME);
275 group->set_attribute(OBJ_NAME, raw);
276 group->lock_attribute(OBJ_NAME);
277 // re-mount group in home module:
278 if ( is_mounted ) get_module( "home" )->mount( group );
280 _Persistence->uncache_object( group );
282 // notify persistence layers:
283 _Persistence->group_renamed( group, old_name, new_name );
284 // update sub-groups' names:
285 foreach(group->get_sub_groups(), object subgroup) {
286 if ( objectp(subgroup) && subgroup->status() > 0 ) {
287 // this is not actually a move, but should update name:
288 rename_group(subgroup, subgroup->query_attribute(OBJ_NAME));
294 object fix_group_parent ( object group )
296 _SECURITY->check_access(group,CALLER,SANCTION_WRITE,ROLE_WRITE_ALL,false);
297 object parent = group->get_parent();
298 if ( objectp(parent) ) return parent;
299 string identifier = group->get_identifier();
300 array parts = identifier / ".";
301 if ( sizeof(parts) < 2 ) return 0; // top-level group
302 string parent_identifier = parts[0..(sizeof(parts)-2)] * ".";
303 parent = get_module("groups")->lookup( parent_identifier );
304 if ( objectp(parent) ) group->set_parent( parent );
308 void delete_group ( object group )
310 // check for write access on group and all sub-groups (recursively):
311 _SECURITY->check_access(group,CALLER,SANCTION_WRITE,ROLE_WRITE_ALL,false);
312 foreach ( group->get_sub_groups_recursive(), object subgroup )
313 _SECURITY->check_access(subgroup,CALLER,SANCTION_WRITE,ROLE_WRITE_ALL,false);
314 // unmount group from home module:
315 get_module( "home" )->unmount( group );
316 // delete group recursively with it's creator as euid:
317 object old_euid = geteuid();
318 object delete_euid = group->get_creator();
319 if ( !objectp(delete_euid) ) delete_euid = _ROOT;
320 seteuid( delete_euid );
321 mixed err = catch( group->low_delete_object() );
323 if ( err ) throw( err );
327 string name = sprintf("test%d", time() );
328 object grp = execute( (["name": name, ]) );
329 if ( objectp(grp) ) test_objects += ({ grp });
330 Test.test( "creating group", objectp(grp) );
331 if ( !objectp(grp) ) return;
332 object grp2 = execute( (["name": name+"_tmp", ]) );
333 if ( objectp(grp2) ) test_objects += ({ grp2 });
334 Test.test( "forbid renaming group with same name",
335 !rename_group(grp, name) );
336 Test.test( "forbid renaming group to an existing name",
337 !rename_group(grp, grp2->get_identifier()) );
338 Test.test( "forbid renaming group to a name with a '.' in it",
339 !rename_group(grp, "Groups."+name) );
341 GROUP("Groups")->add_member(grp);
342 // changing group name is delayed ....
343 Test.add_test_function( test_more, 1, grp, name );
344 test_load_group(grp);
347 void test_load_group(object grp)
349 object uf = get_factory(CLASS_USER);
350 int tt = get_time_millis();
351 for (int i = 0; i < 500; i++) {
352 object u = _Persistence->lookup_user("grouptester" + i);
356 u = uf->execute( (["name": "grouptester" + i, "pw":"test", ]));
358 werror("500 User created and joined test group in " +
359 (get_time_millis() - tt) / 1000 + " seconds\n");
360 tt = get_time_millis();
361 grp->add_member(USER("root"));
362 werror("join group of this user in " + (get_time_millis() - tt) + "ms\n");
363 tt = get_time_millis();
364 grp->remove_member(USER("root"));
365 werror("leave group of this user in " + (get_time_millis() - tt) + "ms\n");
366 for (int i = 0; i < 500; i++) {
367 object u = USER("grouptester"+i);
373 void test_more(object grp, string name)
375 Test.test( "adding group to another group changes the identifier",
376 has_prefix( grp->get_identifier(), "Groups." ) );
377 Test.test( "forbid renaming group with same parent name",
378 !rename_group(grp, name));
379 Test.test( "Moved group is still a top-level-group!",
380 search(get_module("groups")->get_top_groups(), grp)==-1);
382 string new_name = sprintf("bingo%d", time());
383 Test.test( "renaming group",
384 rename_group(grp, new_name) );
385 Test.test( "renamed group still has parent prefix",
386 has_prefix( grp->get_identifier(), "Groups." ) );
387 Test.test( "renamed group can be found under the new name",
388 get_module("groups")->lookup( "Groups."+new_name ) == grp );
389 Test.test( "old name of renamed group is no longer valid",
390 !objectp(get_module("groups")->lookup( name )) );
391 Test.test("group does not have a workroom!", grp->get_workroom());
392 Test.test( "workroom path has been updated",
393 grp->get_workroom()->query_attribute(OBJ_PATH)
394 == "/home/"+grp->get_identifier() );
395 Test.test( "workroom path in home module has been updated",
396 grp->get_workroom() == OBJ( "/home/"+grp->get_identifier() ) );
401 void test_cleanup () {
402 if ( arrayp(test_objects) ) {
403 foreach ( test_objects, object obj )
404 catch( obj->delete() );
408 string get_identifier() { return "Group.factory"; }
409 string get_class_name() { return "Group"; }
410 int get_class_id() { return CLASS_GROUP; }