GroupFactory._pike
Go to the documentation of this file.
1 /* Copyright (C) 2000-2010 Thomas Bopp, Thorsten Hampel, Ludger Merkens
2  *
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.
7  *
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.
12  *
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
16  *
17  * $Id: GroupFactory.pike,v 1.7 2010/08/20 20:42:25 astra Exp $
18  */
19 inherit "/factories/ObjectFactory";
20 #include <macros.h>
21 #include <classes.h>
22 #include <access.h>
23 #include <roles.h>
24 #include <database.h>
25 #include <events.h>
26 #include <attributes.h>
27 #include <types.h>
28 #include <exception.h>
29 //! This factory creates a group with group workarea.
30 class GroupFactory : public ObjectFactory{
31 public:
32 
33 
34 
35 
36 
37 import Attributes;
38 
39 
40 protected:
41  array
42  sReservedNames = ({ "steam", "admin", "everyone", "privgroups" });
43 
44 public:
45 
46 private array test_objects = ({ });
47 
48 private:
49  void init_factory()
50 {
51  ::init_factory();
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",
59  CMD_TYPE_INT, 0));
60  register_attribute(UserAttribute(GROUP_MSG_ACCEPT,"Group Accept Message",
61  CMD_TYPE_STRING, 0));
62 }
63 
64 public:
65 
66 /**
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".
72  * Params:
73  * string name - the name for the new group(without parents)
74  * object parentgroup - the parent group object.
75  *
76  * @param mapping vars - the parameters within a mapping (see function docs)
77  * @return the created group object or 0
78  * @see group_add_user
79  */
80 object execute(mapping vars)
81 {
82  object grp, parentgroup;
83  string name;
84 
85  name = vars["name"];
86  parentgroup = vars["parentgroup"];
87  // check if the parent group can be used
88 
89  if ( search(name, ".") >= 0 )
90  steam_error("Using '.' in group names is forbidden !");
91 
92  if ( objectp(parentgroup) ) {
93  _SECURITY->check_access(
94  parentgroup, CALLER, SANCTION_INSERT, ROLE_INSERT_ALL ,false);
95  name = parentgroup->get_identifier() + "." + name;
96  }
97  else
98  _SECURITY->check_access(
99  SANCTION_WRITE,
100  ROLE_CREATE_TOP_GROUPS,
101  false);
102 
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.",
107  E_ACCESS);
108  THROW("Group with that name ("+name+") already exists!", E_ACCESS);
109  }
110  ogrp = MODULE_USERS->lookup(name);
111  if ( objectp(ogrp) )
112  steam_error("There is already a user named '"+name+"' !");
113 
114  try_event(EVENT_EXECUTE, CALLER, grp);
115  grp = object_create(name, CLASS_NAME_GROUP, 0,
116  vars["attributes"],
117  vars["attributesAcquired"],
118  vars["attributesLocked"],
119  vars["sanction"],
120  vars["sanctionMeta"]);
121 
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");
125 
126  grp->set_group_name(name);
127  grp_set_attribute(OBJ_NAME, vars->name);
128  grp_lock_attribute(OBJ_NAME);
129 
130  if ( objectp(parentgroup) ) {
131  grp->set_parent(parentgroup);
132 
133  if ( parentAddMember != 1 ) {
134  }
135  }
136 
137  object workroom, factory;
138 
139  factory = _Server->get_factory(CLASS_ROOM);
140 
141  workroom = factory->execute(([
142  "name":vars->name+"'s workarea",
143  ]));
144  grp_set_attribute(GROUP_WORKROOM, workroom);
145  grp_lock_attribute(GROUP_WORKROOM);
146 
147  object steam = GROUP("steam");
148  if ( objectp(steam) )
149  grp_sanction_object(steam, SANCTION_READ); // make readable
150 
151 
152  object calendar=_Server->get_factory(CLASS_CALENDAR)->execute(([
153  "name":name+"'s calendar",
154  "attributesLocked": ([ CALENDAR_OWNER: 1, ]),
155  ]) );
156  grp_set_attribute(GROUP_CALENDAR, calendar);
157  grp_lock_attribute(GROUP_CALENDAR);
158 
159  if ( mappingp(vars["exits"]) )
160  grp_set_attribute(GROUP_EXITS, vars["exits"]);
161  else
162  grp_set_attribute(GROUP_EXITS, ([ workroom:
163  workroom->get_identifier(), ]));
164  run_event(EVENT_EXECUTE, CALLER, grp);
165 
166 }
167 
168 object find_parent(object group)
169 {
170  if ( objectp(group) )
171  return group->get_parent();
172  else
173  return 0;
174 }
175 
176 
177 /**
178  * Move a group to a new parent group. Everything is updated accordingly.
179  *
180  * @param object group - the group to move
181  * @param object new_parent - the new parent group
182  * @return true or false
183  */
184 bool move_group(object group, object new_parent)
185 {
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!");
190 
191  _SECURITY->check_access(group,CALLER,SANCTION_WRITE,ROLE_WRITE_ALL,false);
192 
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!");
199  }
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);
205  }
206 
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 );
210 
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);
215  }
216  if ( !new_parent->is_member(group) )
217  new_parent->add_member(group);
218 
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);
222 
223  // re-mount group in home module:
224  if ( is_mounted ) get_module( "home" )->mount( group );
225 
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
230  }
231  }
232  object workroom = group->query_attribute(GROUP_WORKROOM);
233  workroom->update_path();
234  return true;
235 }
236 
237 string get_group_name(object group)
238 {
239  string identifier = group->get_identifier();
240  array gn = (identifier / ".");
241  if ( sizeof(gn) == 0 )
242  return identifier;
243  return gn[sizeof(gn)-1];
244 }
245 
246 bool rename_group(object group, string new_name)
247 {
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");
251 
252  object parent = find_parent(group);
253  string raw = new_name;
254  if ( objectp(parent) )
255  new_name = parent->get_identifier() + "." + new_name;
256 
257  if ( new_name == group->get_group_name() )
258  return false;
259 
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 )
263  return false;
264 
265  _Persistence->uncache_object( group );
266 
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 );
271  // rename 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 );
279 
280  _Persistence->uncache_object( group );
281 
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));
289  }
290  }
291  return true;
292 }
293 
294 object fix_group_parent ( object group )
295 {
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 );
305  return parent;
306 }
307 
308 void delete_group ( object group )
309 {
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() );
322  seteuid( old_euid );
323  if ( err ) throw( err );
324 }
325 
326 {
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) );
340 
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);
345 }
346 
347 void test_load_group(object grp)
348 {
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);
353  if (objectp(u)) {
354  u->delete();
355  }
356  u = uf->execute( (["name": "grouptester" + i, "pw":"test", ]));
357  }
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);
368  u->delete();
369  }
370 }
371 
372 protected:
373  void test_more(object grp, string name)
374 {
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);
381 
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() ) );
397 }
398 
399 public:
400 
401 void test_cleanup () {
402  if ( arrayp(test_objects) ) {
403  foreach ( test_objects, object obj )
404  catch( obj->delete() );
405  }
406 }
407 
408 string get_identifier() { return "Group.factory"; }
409 string get_class_name() { return "Group"; }
410 int get_class_id() { return CLASS_GROUP; }
411 
412 
413 };