/* ** j###t ########## #### #### ** j###t ########## #### #### ** j###T "###L J###" ** ######P' ########## ######### ** ######k, ########## T######T ** ####~###L #### ** #### q###L ########## .##### ** #### \###L ########## #####" ** ** $Id$ ** ** Class History ** ** Date Name Description ** ---------|------------|----------------------------------------------- ** 19Aug98 subtle start of recorded history ** */ package key; import key.util.EmptyEnumeration; import java.io.IOException; import java.io.DataOutput; import java.io.DataInput; import java.util.Enumeration; import java.util.StringTokenizer; import java.util.Vector; public class PermissionList implements java.io.Serializable { private static final long serialVersionUID = 6961040118922549304L; Vector entries; Atom parent; Entry defaults; public static final int DEFAULT_LIMIT = 20; private int limit; public PermissionList( Atom p ) { entries = null; parent = p; defaults = new Entry( null ); limit = DEFAULT_LIMIT; } private final void ensureList() { if( entries == null ) entries = new Vector( 2, 5 ); } public final void deallocate() { if( entries != null ) { if( entries.size() == 0 ) entries = null; else entries.trimToSize(); } } public final void clear() { entries.setSize( 0 ); } public final void setLimit( int i ) { limit = i; } public final int getLimit() { return( limit ); } public final boolean actionRegistered( Action a ) { return( parent.containsAction( a ) ); } public Enumeration elements() { if( entries != null ) return( entries.elements() ); else return( new EmptyEnumeration() ); } public int count() { if( entries != null ) return( entries.size() ); else return( 0 ); } public void allow( Action action ) { defaults.allow( action ); } public void deny( Action action ) { defaults.deny( action ); } public void allow( Reference ref, Action action ) { if( !actionRegistered( action ) ) throw new UnexpectedResult( "action '" + action + "' not registered for permission list belonging to " + parent.getName() ); Entry ple = getEntryFor( ref ); if( ple == null ) { ple = new Entry( ref ); newEntry( ple ); } ple.allow( action ); } public void deny( Reference ref, Action action ) { if( !actionRegistered( action ) ) throw new UnexpectedResult( "action '" + action + "' not registered for permission list belonging to " + parent.getName() ); Entry ple = getEntryFor( ref ); if( ple == null ) { ple = new Entry( ref ); newEntry( ple ); } ple.deny( action ); } public void clear( Reference ref, Action action ) { if( entries != null ) { if( !actionRegistered( action ) ) throw new UnexpectedResult( "action '" + action + "' not registered for permission list belonging to " + parent.getName() ); Entry ple = getEntryFor( ref ); if( ple == null ) return; if( ple.clear( action ) ) { entries.removeElement( ple ); // if that was the last thing in it, // tidy it up. if( entries.size() == 0 ) entries = null; } } } public void deleteEntryFor( Reference ref ) { if( entries != null ) { Entry ple = getEntryFor( ref ); if( ple != null ) { entries.removeElement( ple ); if( entries.size() == 0 ) entries = null; } } } public Entry getEntryFor( Reference ref ) { for( Enumeration e = elements(); e.hasMoreElements(); ) { Entry ple = (Entry) e.nextElement(); if( ple.isFor( ref ) ) return( ple ); } return( null ); } public boolean isAllowingByDefault( Action action ) { return( defaults.isAllowing( action ) ); } public boolean check( Reference ref, Action action ) { if( !actionRegistered( action ) ) throw new UnexpectedResult( "action '" + action + "' not registered for permission list belonging to " + parent.getName() ); Entry ple = getEntryFor( ref ); if( ple != null ) { //System.out.println( " found permission entry for tag '" + tag.getName() + "'" ); if( ple.isAllowing( action ) ) { //System.out.println( " action " + action.getName() + " is explicitly allowed" ); return( true ); } else if( ple.isDenying( action ) ) { //System.out.println( this, " action " + action.getName() + " is explicitly denied" ); return( false ); } } //else //System.out.println( " no entry for tag '" + tag.getName() + "'" ); //System.out.println( "Using default for action " + action.getName() + " which is " + (action.allowByDefault() ? "to allow" : "to deny" ) ); return( defaults.isAllowing( action ) ); } void violation( Action action, String s ) { if( action.getSerious() ) throw new AccessViolationException( parent, s ); else throw new PermissionDeniedException( parent, s ); } public void check( Action action ) { if( permissionCheck( action, true, true ) ) return; else { try { Animated s = (Animated) Thread.currentThread(); violation( action, s.getName() + " trying to " + action.getName() + " " + parent.getId() ); } catch( ClassCastException e ) { violation( action, "trying to " + action.getName() + " " + parent.getId() ); } } } /** * Checks the permissions for the currently running * thread. This is (possibly), a very inefficient * routine. This is permitted because priv checks * are relatively rare things. (Aside from the * first 'list lookup') */ public boolean permissionCheck( Action action, boolean noisy, boolean checkRanks ) { try { Thread t = Thread.currentThread(); if( t instanceof Animated ) { Animate s = ((Animated)t).is(); // this is an 'efficiency' hack for playerfiles // it'll just shortcut if we're trying to modify // ourselves if( s == parent ) { //Log.debug( this, "permission check on ourselves shortcutted for action '" + action.getName() + " " + parent.getName() + "'" ); return true; } // this can be changed back to Atom when we // allow people to write their own code. if( s instanceof InteractiveConnection ) { Player a = ((InteractiveConnection)s).getPlayer(); if( a != null ) { //System.out.println( "PermissionList.check, targeter " + ((Atom)s).getName() + ", action " + action.getName() ); // META: remove this bit - there's a better search // for beyond further down //if( a.isBeyond() ) //return true; // first off - if they own us, they can do // whatever they want. A bit scary, otherwise. if( parent.isOwner( a ) ) { //System.out.println( "permission check on atom " + parent.getName() + ", owner " + a.getName() + " suceeded to " + action.getName() + " it" ); return true; } /* else { System.out.println( a.getId() + " is not the owner of " + parent.getId() ); Atom p = parent.getOwner(); if( p != null ) System.out.println( " , " + p.getId() + " is." ); else System.out.println( " , it is null" ); } */ // now do the standard permissions check //System.out.println( a.getName() + " wants to " + action.getName() + " " + parent.getName() ); if( check( a.getThis(), action ) ) return true; else { // check the permissions of the container its in // (this is a way of saying "anything in the 'groups' // container can do x") It is *NOT* recursive at // the moment. // // as an efficiency consideration, just use // ranks at the moment - that is, don't allow // entries to work for any arbitrary container, // just ranks and animated atoms // // that means we use 's' instead of a, since its // a player. it also means we'll need to notice // and change this when we allow people to write // their own code. // // this is more efficient, as, generally, people // are only in one or two ranks. // // now, while we're searching ranks, we might as // well do the targetting stuff - that is, // check that if the rank of the target allows // us to target, well, all the rank's we're in, // blah, then its okay, you know? // // You might want to read the documentation // (hopefully we've produced some and I've found // the design I did) if you don't understand // how ranks are checked. // // here we are stepping through the ranks of the // player _doing_ the action to the current // permission list. // Targetable rent = null; Atom new_rent = parent; while( new_rent != null && !(new_rent instanceof Targetable) ) { new_rent = new_rent.getParent(); } rent = (Targetable) new_rent; /* -- META this does something, surely if( rent != null && ((Atom)rent).isLoading() ) return true; */ // check clan and friends permissions if( rent instanceof Player ) { Player p = (Player) rent; Friends f = p.getFriends(); Clan c = p.getClan(); //System.out.println( "checking friends list: " + f.getId() + " against player " + a.getId() ); if( f.containsPlayer( a ) && check( f.getThis(), action ) ) return true; //System.out.println( "checking clan..." ); if( c != null && c.containsPlayer( (Player) a ) && check( c.getThis(), action ) ) return true; } if( checkRanks ) { // if any of the ranks that the player is in // we have a list entry allowing them to do it, or // has rent (us or our parent) in its target list for( Enumeration e = a.ranks(); e.hasMoreElements(); ) { Rank rank = (Rank) e.nextElement(); if( check( rank.getThis(), action ) ) { InteractiveConnection ic = ((Player)a).getConnection(); if( ic != null && noisy ) ic.sendSystem( "(" + rank.getName() + " used to '" + action.getName() + " " + parent.getName() + "')" ); return true; } // this call is another loop - // this is a very inefficient place to be // a loop if( rent != null && rent.isOutRankedBy( rank ) ) { InteractiveConnection ic = ((Player)a).getConnection(); if( ic != null && noisy ) ic.sendSystem( "(" + rank.getName() + " used to '" + action.getName() + " " + parent.getName() + "')" ); return true; } } } // as a last resort - see if they've got beyond if( a instanceof Player && ((Player)a).isBeyond() ) { InteractiveConnection ic = ((Player)a).getConnection(); if( ic != null && noisy ) ic.sendSystem( "(beyond used to '" + action.getName() + " " + parent.getName() + "')" ); return true; } return( false ); } } else { // META //System.out.println( "perm check when IC not set..." ); } } } } catch( Exception e ) { Log.error( e ); return( false ); } return( true ); } protected void newEntry( Entry ent ) { ensureList(); entries.addElement( ent ); } public String toString() { return( toString( true ) ); } public String toString( boolean expert ) { if( count() == 0 ) { return( "No list entries" ); } else { StringBuffer sb = new StringBuffer(); sb.append( Integer.toString( count() ) + " entr" + ((count()==1) ? "y" : "ies") + " in the list:\n" ); for( Enumeration e = elements(); e.hasMoreElements(); ) { Entry ple = (Entry) e.nextElement(); sb.append( " " ); sb.append( ple.toString( expert ) ); sb.append( "\n" ); } return( sb.toString() ); } } public final Atom getParent() { return( parent ); } public Immutable getImmutable() { return( new Immutable() ); } public static final class Entry implements java.io.Serializable { Vector allowActions; Vector denyActions; Reference entryFor; public Entry( Reference entry ) { if( entry == null ) entryFor = null; else entryFor = entry; allowActions = new Vector( 0, 3 ); denyActions = new Vector( 0, 3 ); } public int allowCount() { return( allowActions.size() ); } public int denyCount() { return( denyActions.size() ); } public int count() { return( allowCount() + denyCount() ); } public boolean isAllowing( Action action ) { //Key.instance().debug( this, " isAllowing called: entryFor " + entryFor.getName() + " contains " + count() + " (" + allowCount() + "/" + denyCount() + ") entries" ); if( allowActions.contains( action ) ) return( true ); else return( false ); } public boolean isDenying( Action action ) { //Key.instance().debug( this, " isDenying called: entryFor " + entryFor.getName() + " contains " + count() + " (" + allowCount() + "/" + denyCount() + ") entries" ); if( denyActions.contains( action ) ) return( true ); else return( false ); } public Enumeration allowing() { return( allowActions.elements() ); } public Enumeration denying() { return( denyActions.elements() ); } public boolean isFor( Reference test ) { return( test.equals( entryFor ) ); } public void allow( Action toAllow ) { if( allowActions.contains( toAllow ) ) return; if( denyActions.contains( toAllow ) ) denyActions.removeElement( toAllow ); allowActions.addElement( toAllow ); } public void deny( Action toDeny ) { if( denyActions.contains( toDeny ) ) return; if( allowActions.contains( toDeny ) ) allowActions.removeElement( toDeny ); denyActions.addElement( toDeny ); } /** * Returns true if this entry now * contains nothing. */ public boolean clear( Action toDefault ) { if( allowActions.contains( toDefault ) ) allowActions.removeElement( toDefault ); if( denyActions.contains( toDefault ) ) denyActions.removeElement( toDefault ); if( count() == 0 ) return true; return false; } public String toString() { return( toString( true ) ); } public String toString( boolean expert ) { StringBuffer sb = new StringBuffer(); boolean empty = true; if( entryFor == null ) sb.append( "defaults" ); else sb.append( entryFor.getName() ); sb.append( " -" ); if( allowCount() > 0 ) { boolean first = true; sb.append( " [Allow: " ); for( Enumeration e = allowing(); e.hasMoreElements(); ) { Action a = (Action)e.nextElement(); boolean exp = a.getExpert(); if( (exp && expert) || !exp ) { if( !first ) sb.append( ", " ); first = false; sb.append( a.getName() ); } } sb.append( "]" ); empty = false; } if( denyCount() > 0 ) { boolean first = true; sb.append( " [Deny: " ); for( Enumeration e = denying(); e.hasMoreElements(); ) { Action a = (Action)e.nextElement(); boolean exp = a.getExpert(); if( (exp && expert) || !exp ) { if( !first ) sb.append( ", " ); first = false; sb.append( a.getName() ); } } sb.append( "]" ); empty = false; } // this shouldn't ever happen, but you never know if( empty ) sb.append( "[]" ); return( sb.toString() ); } } public class Immutable { public final int getLimit() { return( PermissionList.this.getLimit() ); } public final boolean actionRegistered( Action a ) { return( PermissionList.this.actionRegistered( a ) ); } public final int count() { return( PermissionList.this.count() ); } public final boolean isAllowingByDefault( Action action ) { return( PermissionList.this.isAllowingByDefault( action ) ); } public final boolean check( Reference ref, Action action ) { return( PermissionList.this.check( ref, action ) ); } public final void check( Action action ) { PermissionList.this.check( action ); } public final boolean permissionCheck( Action action, boolean noisy, boolean checkRanks ) { return( PermissionList.this.permissionCheck( action, noisy, checkRanks ) ); } public final String toString( boolean expert ) { return( PermissionList.this.toString( expert ) ); } public final String toString() { return( PermissionList.this.toString() ); } public final Atom getParent() { return( PermissionList.this.getParent() ); } } }