• Views
  • Iteration Report
  • My Iteration Report
  •  
OMERO.server
  • Login
  • Help/Guide
  • About Trac
  • Preferences
  • Wiki
  • Timeline
  • Roadmap
  • Browse Source
  • View Tickets
  • Search

Context Navigation

  • Last Change
  • Annotate
  • Revision Log

root/trunk/components/server/src/ome/logic/AdminImpl.java

Revision 2971, 43.2 kB (checked in by ola, 3 months ago)

update for r2970, checkPassword didn't authenticate in ldap

  • Property svn:keywords set to
    Date
    Revision
    Id
    URL
Line 
1/*
2 * ome.logic.AdminImpl
3 *
4 *   Copyright 2006 University of Dundee. All rights reserved.
5 *   Use is subject to license terms supplied in LICENSE.txt
6 */
7
8package ome.logic;
9
10import java.sql.SQLException;
11import java.util.ArrayList;
12import java.util.HashMap;
13import java.util.HashSet;
14import java.util.Iterator;
15import java.util.List;
16import java.util.Map;
17import java.util.Set;
18
19import javax.annotation.security.PermitAll;
20import javax.annotation.security.RolesAllowed;
21import javax.ejb.Local;
22import javax.ejb.Remote;
23import javax.ejb.Stateless;
24import javax.ejb.TransactionManagement;
25import javax.ejb.TransactionManagementType;
26import javax.interceptor.Interceptors;
27
28import ome.annotations.NotNull;
29import ome.annotations.RevisionDate;
30import ome.annotations.RevisionNumber;
31import ome.api.IAdmin;
32import ome.api.ServiceInterface;
33import ome.api.local.LocalAdmin;
34import ome.api.local.LocalLdap;
35import ome.api.local.LocalUpdate;
36import ome.conditions.ApiUsageException;
37import ome.conditions.AuthenticationException;
38import ome.conditions.InternalException;
39import ome.conditions.SecurityViolation;
40import ome.conditions.ValidationException;
41import ome.model.IObject;
42import ome.model.internal.Permissions;
43import ome.model.internal.Permissions.Flag;
44import ome.model.meta.Event;
45import ome.model.meta.Experimenter;
46import ome.model.meta.ExperimenterGroup;
47import ome.model.meta.GroupExperimenterMap;
48import ome.parameters.Filter;
49import ome.parameters.Parameters;
50import ome.security.ACLVoter;
51import ome.security.AdminAction;
52import ome.security.LdapUtil;
53import ome.security.PasswordUtil;
54import ome.security.SecureAction;
55import ome.security.SecuritySystem;
56import ome.security.basic.BasicSecuritySystem;
57import ome.security.basic.UpdateEventListener;
58import ome.services.query.Definitions;
59import ome.services.query.Query;
60import ome.services.query.QueryParameterDef;
61import ome.services.sessions.events.UserGroupUpdateEvent;
62import ome.services.util.OmeroAroundInvoke;
63import ome.system.EventContext;
64import ome.system.OmeroContext;
65import ome.system.Roles;
66import ome.system.SimpleEventContext;
67import ome.tools.hibernate.HibernateUtils;
68import ome.util.Utils;
69
70import org.hibernate.Criteria;
71import org.hibernate.EmptyInterceptor;
72import org.hibernate.HibernateException;
73import org.hibernate.Session;
74import org.hibernate.SessionFactory;
75import org.hibernate.criterion.Restrictions;
76import org.jboss.annotation.ejb.LocalBinding;
77import org.jboss.annotation.ejb.RemoteBinding;
78import org.jboss.annotation.ejb.RemoteBindings;
79import org.springframework.beans.BeansException;
80import org.springframework.context.ApplicationContext;
81import org.springframework.context.ApplicationContextAware;
82import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
83import org.springframework.mail.MailSender;
84import org.springframework.mail.SimpleMailMessage;
85import org.springframework.orm.hibernate3.HibernateCallback;
86import org.springframework.orm.hibernate3.SessionFactoryUtils;
87import org.springframework.transaction.annotation.Transactional;
88import org.springframework.util.Assert;
89
90/**
91 * Provides methods for administering user accounts, passwords, as well as
92 * methods which require special privileges.
93 *
94 * Developer note: As can be expected, to perform these privileged the Admin
95 * service has access to several resources that should not be generally used
96 * while developing services. Misuse could circumvent security or auditing.
97 *
98 * @author Josh Moore, josh.moore at gmx.de
99 * @version $Revision:1754 $, $Date:2007-08-20 10:36:07 +0100 (Mon, 20 Aug 2007) $
100 * @see SecuritySystem
101 * @see Permissions
102 * @since 3.0-M3
103 */
104@TransactionManagement(TransactionManagementType.BEAN)
105@Transactional
106@RevisionDate("$Date:2007-08-20 10:36:07 +0100 (Mon, 20 Aug 2007) $")
107@RevisionNumber("$Revision:1754 $")
108@Stateless
109@Remote(IAdmin.class)
110@RemoteBindings( {
111        @RemoteBinding(jndiBinding = "omero/remote/ome.api.IAdmin"),
112        @RemoteBinding(jndiBinding = "omero/secure/ome.api.IAdmin", clientBindUrl = "sslsocket://0.0.0.0:3843") })
113@Local(IAdmin.class)
114@LocalBinding(jndiBinding = "omero/local/ome.api.IAdmin")
115@Interceptors( { OmeroAroundInvoke.class, SimpleLifecycle.class })
116public class AdminImpl extends AbstractLevel2Service implements LocalAdmin,
117        ApplicationContextAware {
118
119    /**
120     * Action used by various methods to save objects with the blessing of the
121     * {@link SecuritySystem}.Only the first object will be saved and returned,
122     * but all of the varargs will be given a token.
123     *
124     * @see SecuritySystem#doAction(IObject, SecureAction)
125     */
126    private static class SecureUpdate implements SecureAction {
127        protected final LocalUpdate iUpdate;
128
129        SecureUpdate(LocalUpdate iUpdate) {
130            this.iUpdate = iUpdate;
131        }
132
133        public <T extends IObject> T updateObject(final T... objs) {
134            return iUpdate.saveAndReturnObject(objs[0]);
135        }
136    };
137
138    /**
139     * Action used to flush already saved objects. The top-level objects will be
140     * given a token and so can be safely flushed.
141     */
142    private static class SecureFlush extends SecureUpdate {
143        SecureFlush(LocalUpdate iUpdate) {
144            super(iUpdate);
145        }
146
147        @Override
148        public <T extends IObject> T updateObject(T... objs) {
149            iUpdate.flush();
150            return null;
151        }
152    }
153
154    protected transient SimpleJdbcTemplate jdbc;
155
156    protected transient SessionFactory sf;
157
158    protected transient OmeroContext context;
159
160    protected transient MailSender mailSender;
161
162    protected transient SimpleMailMessage templateMessage;
163
164    protected transient LocalLdap ldap;
165
166    protected transient ACLVoter aclVoter;
167
168    /** injector for usage by the container. Not for general use */
169    public final void setJdbcTemplate(SimpleJdbcTemplate jdbcTemplate) {
170        getBeanHelper().throwIfAlreadySet(this.jdbc, jdbcTemplate);
171        jdbc = jdbcTemplate;
172    }
173
174    /** injector for usage by the container. Not for general use */
175    public final void setSessionFactory(SessionFactory sessions) {
176        getBeanHelper().throwIfAlreadySet(this.sf, sessions);
177        sf = sessions;
178    }
179
180    public void setApplicationContext(ApplicationContext ctx)
181            throws BeansException {
182        this.context = (OmeroContext) ctx;
183    }
184
185    public void setMailSender(MailSender mailSender) {
186        this.mailSender = mailSender;
187    }
188
189    public void setTemplateMessage(SimpleMailMessage templateMessage) {
190        this.templateMessage = templateMessage;
191    }
192
193    public void setLdapService(LocalLdap ldapService) {
194        getBeanHelper().throwIfAlreadySet(ldap, ldapService);
195        this.ldap = ldapService;
196        if (!this.ldap.getSetting()) {
197            this.ldap = null;
198        }
199    }
200
201    public void setAclVoter(ACLVoter aclVoter) {
202        getBeanHelper().throwIfAlreadySet(this.aclVoter, aclVoter);
203        this.aclVoter = aclVoter;
204    }
205
206    public Class<? extends ServiceInterface> getServiceInterface() {
207        return IAdmin.class;
208    }
209
210    // ~ LOCAL PUBLIC METHODS
211    // =========================================================================
212
213    @RolesAllowed("user")
214    public Experimenter userProxy(final Long id) {
215        if (id == null) {
216            throw new ApiUsageException("Id argument cannot be null.");
217        }
218
219        Experimenter e = iQuery.get(Experimenter.class, id);
220        return e;
221    }
222
223    @RolesAllowed("user")
224    public Experimenter userProxy(final String omeName) {
225        if (omeName == null) {
226            throw new ApiUsageException("omeName argument cannot be null.");
227        }
228
229        Experimenter e = iQuery.findByString(Experimenter.class, "omeName",
230                omeName);
231
232        if (e == null) {
233            throw new ApiUsageException("No such experimenter: " + omeName);
234        }
235
236        return e;
237    }
238
239    @RolesAllowed("user")
240    public ExperimenterGroup groupProxy(Long id) {
241        if (id == null) {
242            throw new ApiUsageException("Id argument cannot be null.");
243        }
244
245        ExperimenterGroup g = iQuery.get(ExperimenterGroup.class, id);
246        return g;
247    }
248
249    @RolesAllowed("user")
250    public ExperimenterGroup groupProxy(final String groupName) {
251        if (groupName == null) {
252            throw new ApiUsageException("groupName argument cannot be null.");
253        }
254
255        ExperimenterGroup g = iQuery.findByString(ExperimenterGroup.class,
256                "name", groupName);
257
258        if (g == null) {
259            throw new ApiUsageException("No such group: " + groupName);
260        }
261
262        return g;
263    }
264
265    @RolesAllowed("user")
266    public List<Long> getLeaderOfGroupIds(final Experimenter e) {
267        Assert.notNull(e);
268        Assert.notNull(e.getId());
269
270        List<Long> groupIds = iQuery.execute(new HibernateCallback() {
271            public Object doInHibernate(Session session)
272                    throws HibernateException, SQLException {
273                org.hibernate.Query q = session
274                        .createQuery("select g.id from ExperimenterGroup g where g.details.owner.id = :id");
275                q.setParameter("id", e.getId());
276                return q.list();
277            }
278        });
279        return groupIds;
280    }
281
282    @RolesAllowed("user")
283    public List<Long> getMemberOfGroupIds(final Experimenter e) {
284        Assert.notNull(e);
285        Assert.notNull(e.getId());
286
287        List<Long> groupIds = iQuery.execute(new HibernateCallback() {
288            public Object doInHibernate(Session session)
289                    throws HibernateException, SQLException {
290                org.hibernate.Query q = session
291                        .createQuery("select m.parent.id from GroupExperimenterMap m "
292                                + "where m.child.id = :id");
293                q.setParameter("id", e.getId());
294                return q.list();
295            }
296        });
297        return groupIds;
298    }
299
300    @RolesAllowed("user")
301    // TODO copied from getMemberOfGroupIds
302    public List<String> getUserRoles(final Experimenter e) {
303        Assert.notNull(e);
304        Assert.notNull(e.getId());
305
306        List<String> groupNames = iQuery.execute(new HibernateCallback() {
307            public Object doInHibernate(Session session)
308                    throws HibernateException, SQLException {
309                org.hibernate.Query q = session
310                        .createQuery("select m.parent.name from GroupExperimenterMap m "
311                                + "where m.child.id = :id");
312                q.setParameter("id", e.getId());
313                return q.list();
314            }
315        });
316        return groupNames;
317    }
318
319    // ~ User accessible interface methods
320    // =========================================================================
321
322    @RolesAllowed("user")
323    public Experimenter getExperimenter(final long id) {
324        Experimenter e = iQuery.execute(new UserQ(new Parameters().addId(id)));
325
326        if (e == null) {
327            throw new ApiUsageException("No such experimenter: " + id);
328        }
329
330        return e;
331    }
332
333    @RolesAllowed("user")
334    public Experimenter lookupExperimenter(final String omeName) {
335        Experimenter e = iQuery.execute(new UserQ(new Parameters().addString(
336                "name", omeName)));
337
338        if (e == null) {
339            throw new ApiUsageException("No such experimenter: " + omeName);
340        }
341
342        return e;
343    }
344
345    @RolesAllowed("user")
346    public List<Experimenter> lookupExperimenters() {
347        return iQuery.findAllByQuery("select e from Experimenter e "
348                + "left outer join fetch e.groupExperimenterMap m "
349                + "left outer join fetch m.parent g", null);
350    }
351
352    @Transactional(readOnly = true)
353    @RolesAllowed("user")
354    public List<Map<String, Object>> lookupLdapAuthExperimenters() {
355        return LdapUtil.lookupLdapAuthExperimenters(jdbc);
356    }
357
358    @RolesAllowed("user")
359    public String lookupLdapAuthExperimenter(long id) {
360        return LdapUtil.lookupLdapAuthExperimenter(jdbc, id);
361    }
362
363    @RolesAllowed("user")
364    public ExperimenterGroup getGroup(long id) {
365        ExperimenterGroup g = iQuery.execute(new GroupQ(new Parameters()
366                .addId(id)));
367
368        if (g == null) {
369            throw new ApiUsageException("No such group: " + id);
370        }
371
372        return g;
373    }
374
375    @RolesAllowed("user")
376    public ExperimenterGroup lookupGroup(final String groupName) {
377        ExperimenterGroup g = iQuery.execute(new GroupQ(new Parameters()
378                .addString("name", groupName)));
379
380        if (g == null) {
381            throw new ApiUsageException("No such group: " + groupName);
382        }
383
384        return g;
385    }
386
387    @RolesAllowed("user")
388    public List<ExperimenterGroup> lookupGroups() {
389        return iQuery.findAllByQuery("select g from ExperimenterGroup g "
390                + "left outer join fetch g.groupExperimenterMap m "
391                + "left outer join fetch m.child u "
392                + "left outer join fetch u.groupExperimenterMap m2 "
393                + "left outer join fetch m2.parent", null);
394    }
395
396    @RolesAllowed("user")
397    public Experimenter[] containedExperimenters(long groupId) {
398        List<Experimenter> experimenters = iQuery.findAllByQuery(
399                "select e from Experimenter as e left outer "
400                        + "join e.groupExperimenterMap as map left outer join "
401                        + "map.parent as g where g.id = :id", new Parameters()
402                        .addId(groupId));
403        return experimenters.toArray(new Experimenter[experimenters.size()]);
404    }
405
406    @RolesAllowed("user")
407    public ExperimenterGroup[] containedGroups(long experimenterId) {
408        List<ExperimenterGroup> groups = iQuery
409                .findAllByQuery(
410                        "select g from ExperimenterGroup as g left "
411                                + "outer join g.groupExperimenterMap as map left outer "
412                                + "join map.child as e where e.id = :id",
413                        new Parameters().addId(experimenterId));
414        return groups.toArray(new ExperimenterGroup[groups.size()]);
415    }
416
417    // ~ System-only interface methods
418    // =========================================================================
419
420    @RolesAllowed("system")
421    public void synchronizeLoginCache() {
422        context.publishEvent(new UserGroupUpdateEvent(this));
423    }
424
425    @RolesAllowed("user")
426    public void updateSelf(@NotNull
427    Experimenter e) {
428        EventContext ec = getSecuritySystem().getEventContext();
429        final Experimenter self = getExperimenter(ec.getCurrentUserId());
430        self.setFirstName(e.getFirstName());
431        self.setMiddleName(e.getMiddleName());
432        self.setLastName(e.getLastName());
433        self.setEmail(e.getEmail());
434        self.setInstitution(e.getInstitution());
435        getSecuritySystem().runAsAdmin(new AdminAction() {
436            public void runAsAdmin() {
437                iUpdate.flush();
438            }
439        });
440        getBeanHelper().getLogger().info(
441                "Updated own user info: " + self.getOmeName());
442    }
443
444    @RolesAllowed("system")
445    public void updateExperimenter(@NotNull
446    Experimenter experimenter) {
447        String name = experimenter.getOmeName();
448        iUpdate.saveObject(experimenter);
449        getBeanHelper().getLogger().info("Updated user info for " + name);
450    }
451
452    @RolesAllowed("system")
453    public void updateExperimenterWithPassword(@NotNull
454    Experimenter experimenter, String password) {
455        String name = experimenter.getOmeName();
456        iUpdate.saveObject(experimenter);
457        changeUserPassword(name, password);
458        getBeanHelper().getLogger().info(
459                "Updated user info and password for " + name);
460    }
461
462    @RolesAllowed("system")
463    public void updateGroup(@NotNull
464    ExperimenterGroup group) {
465        iUpdate.saveObject(group);
466        getBeanHelper().getLogger().info("Updated group info for " + group);
467    }
468
469    @RolesAllowed("system")
470    public long createUser(Experimenter newUser, String defaultGroup) {
471        // logged via createExperimenter
472        return createExperimenter(newUser, groupProxy(defaultGroup),
473                groupProxy("user"));
474    }
475
476    @RolesAllowed("system")
477    public long createSystemUser(Experimenter newSystemUser) {
478        // logged via createExperimenter
479        return createExperimenter(newSystemUser, groupProxy("system"),
480                groupProxy("user"));
481    }
482
483    @RolesAllowed("system")
484    @SuppressWarnings("unchecked")
485    public long createExperimenter(Experimenter experimenter,
486            ExperimenterGroup defaultGroup, ExperimenterGroup... otherGroups) {
487
488        SecureAction action = new SecureUpdate(iUpdate);
489
490        Experimenter e = copyUser(experimenter);
491        e.getDetails().copy(getSecuritySystem().newTransientDetails(e));
492        e = getSecuritySystem().doAction(action, e);
493        iUpdate.flush();
494
495        GroupExperimenterMap link = linkGroupAndUser(defaultGroup, e);
496        if (null != otherGroups) {
497            for (ExperimenterGroup group : otherGroups) {
498                linkGroupAndUser(group, e);
499            }
500        }
501
502        changeUserPassword(e.getOmeName(), " ");
503
504        getBeanHelper().getLogger().info(
505                "Created user with blank password: " + e.getOmeName());
506        return e.getId();
507    }
508
509    @RolesAllowed("system")
510    @SuppressWarnings("unchecked")
511    public long createExperimenterWithPassword(Experimenter experimenter,
512            String password, ExperimenterGroup defaultGroup,
513            ExperimenterGroup... otherGroups) {
514
515        SecureAction action = new SecureUpdate(iUpdate);
516
517        Experimenter e = copyUser(experimenter);
518        e.getDetails().copy(getSecuritySystem().newTransientDetails(e));
519        e = getSecuritySystem().doAction(action, e);
520        iUpdate.flush();
521
522        GroupExperimenterMap link = linkGroupAndUser(defaultGroup, e);
523        if (null != otherGroups) {
524            for (ExperimenterGroup group : otherGroups) {
525                linkGroupAndUser(group, e);
526            }
527        }
528
529        changeUserPassword(e.getOmeName(), password);
530
531        getBeanHelper().getLogger().info(
532                "Created user with password: " + e.getOmeName());
533        return e.getId();
534    }
535
536    private GroupExperimenterMap linkGroupAndUser(ExperimenterGroup group,
537            Experimenter e) {
538
539        if (group == null || group.getId() == null) {
540            throw new ApiUsageException("Group must be persistent.");
541        }
542
543        group = new ExperimenterGroup(group.getId(), false);
544
545        // ticket:1021 - check for already added groups
546        for (GroupExperimenterMap link : e.unmodifiableGroupExperimenterMap()) {
547            ExperimenterGroup test = link.parent();
548            if (test.getId().equals(group.getId())) {
549                return link; // EARLY EXIT!
550            }
551        }
552
553        GroupExperimenterMap link = e.linkExperimenterGroup(group);
554        link.getDetails().copy(getSecuritySystem().newTransientDetails(link));
555        getSecuritySystem().doAction(new SecureUpdate(iUpdate),
556                userProxy(e.getId()), link);
557        iUpdate.flush();
558        return link;
559    }
560
561    @RolesAllowed("system")
562    public long createGroup(ExperimenterGroup group) {
563        group = copyGroup(group);
564        ExperimenterGroup g = getSecuritySystem().doAction(
565                new SecureUpdate(iUpdate), group);
566
567        getBeanHelper().getLogger().info("Created group: " + g.getName());
568        return g.getId();
569    }
570
571    @RolesAllowed("system")
572    public void addGroups(final Experimenter user,
573            final ExperimenterGroup... groups) {
574        assertManaged(user);
575
576        final List<String> added = new ArrayList<String>();
577
578        Experimenter foundUser = userProxy(user.getId());
579        for (ExperimenterGroup group : groups) {
580            assertManaged(group);
581            ExperimenterGroup foundGroup = groupProxy(group.getId());
582            boolean found = false;
583            for (ExperimenterGroup currentGroup : foundUser
584                    .linkedExperimenterGroupList()) {
585                found |= HibernateUtils.idEqual(foundGroup, currentGroup);
586            }
587            if (!found) {
588                linkGroupAndUser(foundGroup, foundUser);
589                added.add(foundGroup.getName());
590            }
591        }
592
593        getBeanHelper().getLogger().info(
594                String.format("Added user %s to groups %s", userProxy(
595                        user.getId()).getOmeName(), added));
596    }
597
598    @RolesAllowed("system")
599    public void removeGroups(Experimenter user, ExperimenterGroup... groups) {
600        if (user == null) {
601            return;
602        }
603        if (groups == null) {
604            return;
605        }
606
607        Experimenter foundUser = getExperimenter(user.getId());
608        List<Long> toRemove = new ArrayList<Long>();
609        List<String> removed = new ArrayList<String>();
610
611        for (ExperimenterGroup g : groups) {
612            if (g.getId() != null) {
613                toRemove.add(g.getId());
614            }
615        }
616        for (GroupExperimenterMap map : foundUser
617                .<GroupExperimenterMap> collectGroupExperimenterMap(null)) {
618            Long pId = map.parent().getId();
619            Long cId = map.child().getId();
620            if (toRemove.contains(pId)) {
621                ExperimenterGroup p = iQuery.get(ExperimenterGroup.class, pId);
622                Experimenter c = iQuery.get(Experimenter.class, cId);
623                p.unlinkExperimenter(c);
624                getSecuritySystem().doAction(new SecureUpdate(iUpdate), p);
625                removed.add(p.getName());
626            }
627        }
628        iUpdate.flush();
629
630        getBeanHelper().getLogger().info(
631                String.format("Removed user %s from groups %s", foundUser
632                        .getOmeName(), removed));
633    }
634
635    @RolesAllowed("user")
636    public void setDefaultGroup(Experimenter user, ExperimenterGroup group) {
637        if (user == null) {
638            return;
639        }
640        if (group == null) {
641            return;
642        }
643
644        if (group.getId() == null) {
645            throw new ApiUsageException("Group argument to setDefaultGroup "
646                    + "must be managed (i.e. have an id)");
647        }
648
649        EventContext ec = getSecuritySystem().getEventContext();
650        if (!ec.isCurrentUserAdmin()
651                && !ec.getCurrentUserId().equals(user.getId())) {
652            throw new SecurityViolation("User " + user.getId()
653                    + " can only set own default group.");
654        }
655
656        Roles roles = getSecuritySystem().getSecurityRoles();
657        if (Long.valueOf(roles.getUserGroupId()).equals(group.getId())) {
658            throw new ApiUsageException("Cannot set default group to: "
659                    + roles.getUserGroupName());
660        }
661
662        Experimenter foundUser = getExperimenter(user.getId());
663        ExperimenterGroup foundGroup = getGroup(group.getId());
664        Set<GroupExperimenterMap> foundMaps = foundUser
665                .findGroupExperimenterMap(foundGroup);
666        if (foundMaps.size() < 1) {
667            throw new ApiUsageException("Group " + group.getId() + " was not "
668                    + "found for user " + user.getId());
669        } else if (foundMaps.size() > 1) {
670            getBeanHelper().getLogger().warn(
671                    foundMaps.size() + " copies of " + foundGroup
672                            + " found for " + foundUser);
673        } else {
674            // May throw an exception
675            foundUser.setPrimaryGroupExperimenterMap(foundMaps.iterator()
676                    .next());
677        }
678
679        // TODO: May want to move this outside the loop
680        // and after the !newDefaultSet check.
681        getSecuritySystem().doAction(new SecureUpdate(iUpdate), foundUser);
682
683        getBeanHelper().getLogger().info(
684                String.format("Changing default group for %s to %s", foundUser
685                        .getOmeName(), foundGroup.getName()));
686
687    }
688
689    @RolesAllowed("system")
690    public void setGroupOwner(ExperimenterGroup group, Experimenter owner) {
691        if (owner == null