15

can I find the object who called a method in Java? I have a social network with groups and persons. If a person wants to leave a group, only that can remove itself from the group, nobody else can remove that person, somehow the person who called the method must prove it's identity.

Program-Me-Rev
  • 6,184
  • 18
  • 58
  • 142
John Smith
  • 1,276
  • 4
  • 17
  • 35
  • 2
    Just pass 'this' to the method, but unless you're planning on letting other people call your code I don't see why you're concerned about security. Just don't write any code that allows A to remove B from C, or indeed that allows A to get hold of a reference to B. – user207421 Mar 11 '13 at 00:46

3 Answers3

11

Checking the callers of a method is a quite common request from inexperienced programmers. I'm surprised that it doesn't appear more often on SO. But it's a really incredibly bad idea (just check out the Java 2 (and perhaps worse earlier) Security Model).

There are few circumstances where it's important to implement this restriction. As Oli Charlesworth say, just don't do it. But let's assume that it is important, but we're not going to do stack inspection.

Let's start by assuming we trust the group. A working but nonsensical approach is to pass to the group an object standing in for the person that only the person can create. (Note Java language access restrictions are class-based. A different instance could create such a stand in, but the code would have to be within the Person class.)

public final class Group { // Can't have a malicious subclass.
    public void deregisterPerson(Person.Standin standin) {
        Person person = standin.person();
        ...
    }
}
public class Person { // May be subclassed.
    public final class Standin { // Could be one per Person.
        private Standin() { // Hide constructor from other outer classes.
        }
        public Person person() {
            return Person.this;
        }
    }
    private void groupDeregister(Group group) {
        group.deregisterPerson(new Standin());
    }
 }

If we don't trust the group, then we could extend the stand-in to reference the group. This prevents a malicious group deregistering a person from other groups.

public class Group { // Can have malicious subclasses.
    public void deregisterPerson(Person.GroupDeregister deregister) {
        if (deregister.group() != this) { // Not equals...
            throw new IllegalArgumentException();
        }
        Person person = deregister.person();
        ...
    }
}
public class Person { // May be subclassed.
    public final class GroupDeregister {
        private final Group group;
        private GroupDeregister(Group group) { // Hidden.
            this.group = group;
        }
        public Person person() {
            return Person.this;
        }
        public Group group() {
            return group;
        }
    }
    private void groupDeregister(Group group) {
        group.deregisterPerson(new GroupDeregister(group));
    }
 }

Another approach is to make a "public" version of Person that can be exposed to others.

public class Person { // "PrivatePerson"
    public PublicPerson publicPerson() {
         return new PublicPerson(this);
    }
    private void groupRegister(Group group) {
        group.registerPerson(this);
    }
    private void groupDeregister(Group group) {
        group.deregisterPerson(this);
    }
    ...
}
public class PublicPerson {
    private final Person person;
    public PublicPerson(Person person) {
        this.person = person;
    }
    @Override public final boolean equals(Object obj) {
        return obj instanceof Person && (Person)obj.person == person;
    }
    @Override public final int hashCode() {
        return person.hashCode();
    }
    ...methods, but no raw registration...
 }
 public class Group {
     private final Set<Person> members = new IdentityHashSet<>(); // No Object.equals.
     public void registerPerson(Person person) {
         members.add(person);
     }
     public void deregisterPerson(Person person) {
         members.remove(person);
     }
     public Set<PublicPerson> members() {
         // This will be more concise in Java SE 8.
         Set<PublicPerson> publics = new HashSet<>();
         for (Member member : members) {
             publics.add(member.publicPerson());
         }
         return unmodifiableSet(publics);
     }
}

(Objects.requireNonNull left out for "brevity".)

Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
5

You can do this with reflection by analysing the stack trace (as described in this question: How do I find the caller of a method using stacktrace or reflection?).

However, in most situations, this would be an abuse of reflection. You should strongly consider having your method take an extra argument called caller (which the caller should populate with this).

Community
  • 1
  • 1
Oliver Charlesworth
  • 267,707
  • 33
  • 569
  • 680
  • Yes, but if I want to remove a friend from my list of friends from that group, I can call the method like this: group.removeFriend(myFriend, myFriend //that's the caller). Only the administrator of the group can remove a member. – John Smith Mar 11 '13 at 00:52
  • @JohnSmith: If you're truly worried about that scenario, then add a `protected final` helper method to `Person` (or whatever the base class is), that's responsible for correctly calling the `group.removeFriend` method. – Oliver Charlesworth Mar 11 '13 at 01:06
  • 8
    That link is not relevant: It finds the caller **class**, not the **object**. – Navin May 15 '14 at 05:24
  • 1
    This won't find the class of the calling object. It will find the class in which the method that called this method was defined. If you are trying to find the concrete implementation of an abstract class, this won't necessarily help. – dhasenan Feb 10 '17 at 18:44
  • @dhasenan - not sure I understand. If the goal is to determine the immediate/direct caller, then having that caller pass in `this` should work – Oliver Charlesworth Feb 10 '17 at 22:10
  • Which only works if you control that code. When you're trying to track down a bug in an external library and it's all inversion of control and abstracted to hell and back, a stacktrace doesn't help. – dhasenan Feb 11 '17 at 23:25
0

The risk with Oli's solution is that malicious code could call the 'remove' method and specify a different caller object.

If you are using a security framework such as Spring Security, you can ask for the current principal and only allow removal of that user from the group, or if that user is an admin, allow removal of any user from the group.

Jason
  • 11,744
  • 3
  • 42
  • 46
  • 1
    This is only a problem if you're allowing execution of arbitrary code. Which doesn't sound like a good idea... – Oliver Charlesworth Mar 11 '13 at 00:54
  • The one thing you can guarantee about writing code is that one day someone else will be maintaining it, and they will do something stupid with it. – Jason Mar 11 '13 at 00:57
  • Of course, but that's *always* true. You can't protect against arbitrary stupidity! Whilst I agree with the principle of designing an API so that it's hard to use incorrectly, adding an entire security mechanism just to prevent someone hypothetically passing the wrong argument to a method seems like overkill to me. – Oliver Charlesworth Mar 11 '13 at 00:59
  • I felt it was safe to assume from the OP that users would have to log into this system (in order to identify themselves at the very least). The requirement is to stop users from removing others. This solution guarantees that and reduces the possibility of bugs. – Jason Mar 11 '13 at 01:05