Package framework.beans.security

Source Code of framework.beans.security.LoginBeanAbstarct

/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package framework.beans.security;

import framework.beans.security.passwords.SessionPassword;
import framework.beans.security.passwords.PasswordEncryptor;
import framework.beans.FindEntity;
import framework.beans.FindEntity.Field;
import framework.beans.config.server.ConfigParametrAbstract;
import framework.beans.config.server.ServConfig;
import framework.beans.security.entities.CollaboratorRightAbstract;
import framework.beans.security.entities.CollaboratorSessionActive;
import framework.beans.collaborator.CollaboratorAbstract;
import framework.generic.ClipsServerConstants;
import framework.generic.ClipsServerException;
import framework.generic.ESecurity;
import java.util.Date;
import java.util.BitSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import javax.ejb.Stateful;
import framework.security.UserRightsSetAbstract;
import java.util.Hashtable;
import javax.naming.AuthenticationException;
import javax.naming.AuthenticationNotSupportedException;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

/**
* С подключения к этому бину начинается работа любого клиента. В ответ на авторизацию,
* если всё пройдёт успешно, бин выдаёт идентификатор сессии клиента. И сохранает этот номер в
* таблице активных сессий и у себя.
*
* Клиент должен тормошить сервер с помошью вызова метода DisturbServer() каждые
* ClipsServerConstants.DISTURB_SERVER_PERIOD_MS миллисекунд, чтобы сервер не посчитал, что
* сессия перестала быть активной.
*
* После прекращения работы клиент может вызвать метод logout, чтобы вычеркнуть сессию из
* списка активных немедленно. Если этого не сделать, это будет сделанно автоматически, но позже.
*
* Локальный интерфейс бина предназначен для того, что бы остальные бины могли узнать права
* пользователя по любой указанной сессии. Для использования локального интерфейса нет надобности
* авторизировать бин.
*
* Для ускорения работы бин кеширует права активных сессий. Удаление старых сессий происходит
* перед обращением к бину либо через метод login либо через getSesssionData или getMySessionData,
* но не чаще чем через определённый отрезок времени ClipsServerConstants.AUTO_LOGOUT_PERIOD_MS.
*
* @author antony
*/
public abstract class LoginBeanAbstarct extends FindEntity implements LoginRemoteAbstarct, LoginLocal {

    public static char [] DEFAULT_ADMIN_PASSWORD = "314159".toCharArray();

    private static final HashMap<Integer, SessionSecurityDetails> sessionDataCash = new HashMap<Integer, SessionSecurityDetails>(256, 0.5f);
    private static long lastCleanTime = 0;
    protected SessionPassword sessionPassword;
    private boolean useLDAP = true;


    private void cleanOldSessions() throws ClipsServerException {
        long curTime = System.currentTimeMillis();
        if (curTime - lastCleanTime <= ClipsServerConstants.AUTO_LOGOUT_PERIOD_MS) {
                return;
        }
        try {
            System.out.println("Clean old sessions: " + new Date(curTime));
            Date d = new Date(curTime - ClipsServerConstants.AUTO_LOGOUT_PERIOD_MS);
            Field f[] = {new Field("lastCallMoment", d, Field.OPERATOR_EQUAL_OR_LESS)};
            deleteEntityList(CollaboratorSessionActive.class, f);
            manager.flush();
        } catch (ClipsServerException ex) {
            throw ex;
        } catch (Throwable ex) {
            throw new ClipsServerException("Внутренняя ошибка при очистке таблицы CollaboratorSessionActive (ошибка не критичная, только возникать будет периодически)", ex);
        }

        synchronized (sessionDataCash) {
            Iterator<Integer> it = sessionDataCash.keySet().iterator();
            while (it.hasNext()) {
                Integer e = it.next();
                if (manager.find(CollaboratorSessionActive.class, e) == null) {
                    it.remove();
                }
            }
            lastCleanTime = curTime;
        }
    }

    /**
     * Подготовка к регистарции пользователя в системе.
     * Выдаёт случайные данные, с которой на клиенте следует хешировать хеш пароля.
     * @return - случа
     */
    @Override
    public PasswordEncryptor getEncryptor() {
        if(sessionPassword == null) {
            ServConfig sc = manager.find(ServConfig.class,
                    ConfigParametrAbstract.ID_LDAP_USE);
            String use = (sc == null) ? "false" : sc.getStrvalue();
            useLDAP = Boolean.parseBoolean(use);
            sessionPassword = new SessionPassword(useLDAP);
        }
        return sessionPassword.getEncryptor();
    }

    /**
     * Удостоверяет подлинность пароля пользователя и регистрирует пользователя в системе как активного.
     * Обязательно вызывать этот метод перед началом работы клиента.
     * @param aCollaboratorID
     * @param tryPasswdHash - хеш просоленного хеша пароля, т.е. ХЕШ (ХЕШ(пароля), salt)
     * где salt - результат, полученный функцией queryLogin
   * @return Возвращает идентификатор сессии пользователя - случайное число.
   * @throws Exception
   * @throws ClipsServerException
     */
    @Override
    public int login(Object aCollaboratorID, byte[] tryPasswdHash) throws Exception, ClipsServerException {
        cleanOldSessions();
        CollaboratorAbstract colEntity = findEntity(CollaboratorAbstract.class, (Integer) aCollaboratorID, "сотрудники");
        if (sessionPassword == null) {
            throw new ESecurity("Внутренняя ошибка: Перед вызовом login должен быть вызван getEncryptor ");
        }

        //check is admin or not
        ServConfig sc = manager.find(ServConfig.class, ConfigParametrAbstract.ID_ADMIN_PASSWORD_HASH);
        String adminPassword = (sc == null) ? null : sc.getStrvalue();

        boolean isSuperUser = false;
        if (adminPassword != null && !adminPassword.isEmpty()) {
            isSuperUser = sessionPassword.verifyHash(tryPasswdHash,
                    SessionPassword.char2byte(adminPassword.toCharArray()));
        } else {
            isSuperUser = sessionPassword.verifyPassword(tryPasswdHash, DEFAULT_ADMIN_PASSWORD);
        }

        if (!isSuperUser) {
            if(useLDAP) {
                checkLDAP(colEntity.getLdapName(), tryPasswdHash);
            } else {
                if (colEntity.getPasswordHash() == null) {
                    throw new ESecurity("У Вас не установлен пароль, поэтому вход в систему невозможен. Обратитесь к администратору");
                }

                if (!sessionPassword.verifyHash(tryPasswdHash, colEntity.getPasswordHash())) {
                    sessionPassword = null;
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // DO NOTHING
                    }
                    throw new ESecurity("Введён неверный пароль");
                }

            }
        }

        sessionPassword = null;
    return registerSesion(colEntity, isSuperUser);
    }

  protected int registerSesion(CollaboratorAbstract colEntity, boolean isSuperUser) throws ClipsServerException{
        CollaboratorSessionActive ses;
        Throwable ex = null;
        Random rnd = new Random();
        int sd = 0, newSessionId = 0, i = 0;
        //@todo иногда 10 не хватает
        while (newSessionId == 0 && i < ClipsServerConstants.MAX_LOGIN_ATTEMPT_COUNT) {
            do {
                sd = rnd.nextInt(Integer.MAX_VALUE) + 1;
                ses = manager.find(CollaboratorSessionActive.class, sd);
            } while (ses != null);
            ses = new CollaboratorSessionActive();
            ses.setSessionId(sd);
            ses.setCollaborator(colEntity);
            ses.setLastCallMoment(new Date());
            ses.setAdmin(isSuperUser);
            ses.incRefCount();
            try {
                manager.persist(ses);
                manager.flush();
                newSessionId = sd;
            } catch (Throwable e) {
                ex = e;
            }
            i++;
        }
        if (newSessionId == 0) {
            throw new ClipsServerException("Внутренняя ошибка: невозможно зарегистрировать сессию на сервере", ex);
        }
        return newSessionId;
  }


    /**
     *
     * @param ldapName
     * @param tryPasswdHash
     */
    private void checkLDAP(String ldapName, byte[] tryPasswdHash) throws ClipsServerException {
        Hashtable<String, String> env = new Hashtable<String, String>();
        ServConfig sc = manager.find(ServConfig.class, ConfigParametrAbstract.ID_LDAP_URL);
        String url = (sc == null) ? "" : sc.getStrvalue();

        ServConfig scSSL = manager.find(ServConfig.class, ConfigParametrAbstract.ID_LDAP_USE_SSL);
        Boolean ssl = (scSSL == null) ? false : Boolean.parseBoolean(scSSL.getStrvalue());

        ServConfig scMask = manager.find(ServConfig.class, ConfigParametrAbstract.ID_LDAP_SEARCH_MASK);
        String mask = (scMask == null) ? "$1" : scMask.getStrvalue();
        mask = mask.replaceAll("\\$1", "%s");
        mask = String.format(mask, ldapName);

        ServConfig scType = manager.find(ServConfig.class, ConfigParametrAbstract.ID_LDAP_CRYPTO_SCHEME);
        String crypt = (scType == null) ? "simple" : scType.getStrvalue();

        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.PROVIDER_URL, url);
        env.put(Context.SECURITY_AUTHENTICATION, crypt);
        env.put(Context.SECURITY_PRINCIPAL, mask);

        if(ssl) {
            env.put(Context.SECURITY_PROTOCOL, "ssl");
        }

        String passwd = new String(sessionPassword.decryptPassword(tryPasswdHash));
        env.put(Context.SECURITY_CREDENTIALS, passwd);

        try {
            DirContext ctx = new InitialDirContext(env);
            ctx.close();
        } catch (AuthenticationNotSupportedException ex) {
            String str = "Указанная схема аутентификации не поддерживается сервером LDAP.";
            env.put(Context.SECURITY_AUTHENTICATION, "none");
            env.remove(Context.SECURITY_PRINCIPAL);
            env.remove(Context.SECURITY_CREDENTIALS);
            try {
                DirContext ctx = new InitialDirContext(env);
                Attributes attrs = ctx.getAttributes("ldap://localhost:389", new String[]{"supportedSASLMechanisms"});
                NamingEnumeration<? extends Attribute> modes = attrs.getAll();
                str += "\nПоддерживаемые механизмы SASL: ";
                while(modes.hasMore()) {
                    Attribute attr = modes.next();
                    for(int i=0; i<attr.size(); i++) {
                        str += attr.get(i) + (i+1< attr.size() ? ", ":"");
                    }
                }
                ctx.close();
            } catch (Exception e) {
                e.printStackTrace();
                //do nothing
            }
            throw new ClipsServerException(str);

        } catch(AuthenticationException ex) {
            throw new ClipsServerException("Указан неверный LDAP логин или пароль.");
        } catch (Exception ex) {
            throw new ClipsServerException("Ошибка при попытке аутентификации.", ex);
        }
    }

    /**
     * Вычёркивает сессию из списка активных. Этот метод желательно вызвать по окончании сессии
     * клиента. Но можно и не вызывать.
   * @param aSessionId
   * @throws ClipsServerException
     */
    @Override
    public void logout(int aSessionId) throws ClipsServerException {
        CollaboratorSessionActive ses = null;
        synchronized (sessionDataCash) {
            if (aSessionId != 0) {
                if (sessionDataCash.containsKey(aSessionId)) {
                    sessionDataCash.remove(aSessionId);
                }
                ses = manager.find(CollaboratorSessionActive.class, aSessionId);
            }
        }
        if (ses == null) {
            throw new ClipsServerException("Внутренняя ошибка: Попытка завершить незарегистрированную сессию");
        }
        ses.decRefCount();
        if (ses.getRefCount() == 0) {
            manager.remove(ses);
        } else {
            manager.merge(ses);
        }
    }

    /**
     * Выдаёт информацию о сессии клиента в основном - просто права. Сначала информация ищется
     * в кеше, а потом, если не находится, в таблице активных сессий и в таблице прав пользователей.
     * Если не будет найдена, значит - ошибочный вызов метода, нет такой сессии.
     * Метод доступен только через локальный интерфейс. Будет работать и при не зарегистрированном
     * с помощью login бине.
     * @param aSessionId - идентификатор сессии по которой следует узнать права.
     * @return
     * @throws ClipsServerException
     */
    @Override
    public SessionSecurityDetails getSession(int aSessionId) throws ClipsServerException {
        cleanOldSessions();
        if (aSessionId == 0) {
            throw new ESecurity("Внутренняя ошибка: Пользователь не зарегистрирован. Идентификатор сессии клиента = 0");
        }
        SessionSecurityDetails d;
        synchronized (sessionDataCash) {
            d = sessionDataCash.get(aSessionId);
            if (d == null) {
                CollaboratorSessionActive ses = manager.find(CollaboratorSessionActive.class, aSessionId);
                if (ses == null) {
                    throw new ESecurity("Внутренняя ошибка: Указана несуществующая сессия клиента");
                }
                d = new SessionSecurityDetails();
                d.sessionId = ses.getSessionId();
                d.collaboratorId = ses.getCollaborator().getId();
                d.isSuperUser = ses.isSuperUser();
                d.currentUserRights = getCollaboratorRights(d.collaboratorId, d.isSuperUser);
                sessionDataCash.put(aSessionId, d);
            }
        }
        return d;
    }

    @Override
    public boolean hasSession(int sessionID) {
        synchronized (sessionDataCash) {
            return sessionDataCash.containsKey(sessionID);
        }
    }
    /**
     * Вы даёт информацию, о сессии. Доступен через удалённый интерфейс.
     * Почему то бинс ругается (немного) если один и тот же метод есть в локальном и удалённом
     * интерфейсах. Может и ерунда, но я добавил ещё этот.
   * @param aSessionId
   * @return
     * @throws ClipsServerException
     */
    @Override
    public SessionSecurityDetails getSessionRemote(int aSessionId) throws ClipsServerException {
        //OLD return (SessionSecurityData) ((SessionSecurityDataImp) getSessionData(aSessionId)).clone();
        return getSession(aSessionId);
    }

    private BitSet getCollaboratorRights(int aCollaboratorId, boolean isSuperUser) {
        BitSet bits = new BitSet(UserRightsSetAbstract.ApproxMaxRightsCount);
        if (! isSuperUser) {
            List<CollaboratorRightAbstract> rt = findEntityList(CollaboratorRightAbstract.class, "id.collId", aCollaboratorId);
            for (CollaboratorRightAbstract w : rt) {
                bits.set(((CollaboratorRightAbstract) w).getId().getRightId());
            }
        } else {
            bits.set(0, UserRightsSetAbstract.ApproxMaxRightsCount);
//            Iterator<UserRight> it = UserRightsSetAbstract.elements().iterator();
//            while (it.hasNext()) {
//                bits.set(it.next().getID());
//            }
        }
        return bits;
    }

    /**
     * "Тормошит" сервер, чтобы он не завершал сессию.
   * @param aSessionId
   * @throws ESecurity
     */
    @Override
    public void disturbServer(int aSessionId) throws ESecurity {
        CollaboratorSessionActive ses = null;
        if (aSessionId != 0) {
            ses = manager.find(CollaboratorSessionActive.class, aSessionId);
        }
        if (ses == null) {
            throw new ESecurity("Внутренняя ошибка: Попытка обновить незарегистрированную сессию");
        }
        ses.setLastCallMoment(new Date());
        manager.merge(ses);
    }
}
TOP

Related Classes of framework.beans.security.LoginBeanAbstarct

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.