2011-07-19 7 views
1

私はthisチュートリアルを使ってウェブサイトを作っていますが、いつでも1人のユーザーしかログインできないようにしたいと思います。このCGI :: Applicationでは、一度に1人のユーザーしか許可できません。

この変更は、私が同梱しているLogin.pmで行うべきだと思いますが、この制限をどこに入れるのか分かりません。 scorpio17のソリューションに基づいて

更新

私は今、ユーザーがログアウトをクリックして覚えている場合のみ、1ユーザーは、ログインすることができることを持っています。

問題は、セッションがタイムアウトしたときに$ can_login状態がどのように変更されるかです。

ここに更新された機能があります。

誰でもこれを把握できますか?

package MyLib::Login; 

use strict; 

#use lib '/usr/lib/perl5/vendor_perl/5.8.8/'; 

use base 'CGI::Application'; 

# shorter URLs 
# extract the desired run mode from the PATH_INFO environment variable. 
use CGI::Application::Plugin::AutoRunmode; 

# wrapper for DBI 
#use CGI::Application::Plugin::DBH(qw/dbh_config dbh/); 

# a wrapper around CGI::Session. 
# maintain state from one page view to the next (provides persistent data). 
use CGI::Application::Plugin::Session; 

# logging in and out. 
# Authentication allows to identify individual users. 
use CGI::Application::Plugin::Authentication; 

# external redirects in CGI::Application 
use CGI::Application::Plugin::Redirect; 

# read parameters from config file 
use CGI::Application::Plugin::ConfigAuto(qw/cfg/); 

# encrypt passphrases 
use Digest::MD5 qw(md5_hex); 

# authenticate against NIS/LDAP server 
use Authen::Simple::LDAP; 


sub setup { 
    my $self = shift; 

    $self->mode_param(
    path_info => 1,   # tell CGI::Application to parse the PATH_INFO environment variable 
    param  => 'rm', 
    ); 
} 

# most of the initialization is done here 
sub cgiapp_init { 
    my $self = shift; 

    # read config file and store name-value pairs in %CFG 
    my %CFG = $self->cfg; 

    # where to look for templete files 
    $self->tmpl_path(['./templates']); 

    # save session data in mysql 
    $self->session_config(
    # store sessions in /tmp as files 
    CGI_SESSION_OPTIONS => [ "driver:File", $self->query, {Directory=>'/tmp'} ], 

    DEFAULT_EXPIRY => '+10m', # default expiration time for sessions 
    ); 

    # configure authentication parameters 
    $self->authen->config(
    DRIVER => [ 'Authen::Simple::LDAP', 
      host => 'ldaps://nms.imm.dtu.dk/dc=ldap,dc=imm,dc=dtu,dc=dk', 
      basedn => 'OU=people,DC=ldap,DC=imm,DC=dtu,DC=dk', 
    ], 

    STORE    => 'Session',   # save login state inside a session 
                # If a user is not logged in, but tries to access a 
                # protected page, the Authentication plugin will 
                # automatically redirect the user to the login page. 
                # Once the user enters a valid username and 
                # passsword, they get redirected back to the 
                # protected page they originally requested. 
    LOGOUT_RUNMODE  => 'logout',   # method to use for logging out when session expires 

# uncomment the next 3 lines to enable custom build login prompt 
# LOGIN_RUNMODE  => 'login', 
# POST_LOGIN_RUNMODE => 'okay',    # run mode that gets called after a user successfully logs in 
                # figures out which run mode (page) the user really wanted to 
                # see, then redirects the browser to that page using http 
                # (not https). 

# RENDER_LOGIN   => \&my_login_form, # generate a login form. Authentication plugin comes with a default 
    ); 

    # define runmodes (pages) that require successful login: 
    # The Login.pm module doesn't define any content - all of the actual web pages are in Simple.pm. 
    # 'mustlogin' page is a place-holder. It's a dummy page that forces you to login, but immediately redirects 
    # you back to the default start page (usually the index page). 
    $self->authen->protected_runmodes('mustlogin'); 
} 


# define mustlogin runmode 
sub mustlogin : Runmode { 
    my $self = shift; 
    my $url = $self->query->url; 
    return $self->redirect($url); 
} 


# switch from https to http. It assumes that the target run mode is stored in a cgi parameter named 
# 'destination', but if for some reason this is not the case, it will default back to the index page. 
sub okay : Runmode { 
    my $self = shift; 

    my $url = $self->query->url; 
# my $user = $self->authen->username; 
    my $dest = $self->query->param('destination') || 'index'; 

    if ($url =~ /^https/) { 
    $url =~ s/^https/http/; 
    } 

    return $self->redirect("$url/$dest"); 
} 

# displays the login form 
# But first, it checks to make sure you're not already logged in, and second, it makes sure you're connecting with https. If you try to access the login page with http, it will automatically redirect you using https. 
sub login : Runmode { 
    my $self = shift; 
    my $url = $self->query->url; 

    my $user = $self->authen->username; 
    # is user logged in? 
    if ($user) { 
    my $message = "User $user is already logged in!"; 
    my $template = $self->load_tmpl('default.html'); 
    $template->param(MESSAGE => $message); 
    $template->param(MYURL => $url); 
    return $template->output; 
    } else { 
    my $url = $self->query->self_url; 
    unless ($url =~ /^https/) { 
      $url =~ s/^http/https/; 
     return $self->redirect($url); 
    } 
    return $self->my_login_form; 
    } 
} 

# generate custom login. See templates/login_form.html 
sub my_login_form { 
    my $self = shift; 
    my $template = $self->load_tmpl('login_form.html'); 

    (undef, my $info) = split(/\//, $ENV{'PATH_INFO'}); 
    my $url = $self->query->url; 

    # 'destination' contains the URL of the page to go to once the user has successfully logged in 

    # try to get a value for 'destination' from the CGI query object (in case it was passed as a hidden variable) 
    my $destination = $self->query->param('destination'); 

    # If failed to get from CGI query object, try get destination from PATH_INFO environment variable 
    # in case it's being passed as part of the URL 
    unless ($destination) { 
    if ($info) { 
     $destination = $info; 
    } else { 
     # default to index page 
     $destination = "index"; 
    } 
    } 

    my $error = $self->authen->login_attempts; 

    # insert values into the template parameters 
    $template->param(MYURL => $url); 
    $template->param(ERROR => $error); 
    $template->param(DESTINATION => $destination); 

    # generate final html 
    return $template->output; 
} 
# logout method 
sub logout : Runmode { 
    my $self = shift; 
    if ($self->authen->username) { 
    $self->authen->logout; 
    $self->session->delete; # Delete current session 
    } 
    return $self->redirect($self->query->url); 
} 

# error runmode/page 
sub myerror : ErrorRunmode { 
    my $self = shift; 
    my $error = shift; 
    my $template = $self->load_tmpl("default.html"); 
    $template->param(NAME => 'ERROR'); 
    $template->param(MESSAGE => $error); 
    $template->param(MYURL => $self->query->url); 
    return $template->output; 
} 

# called if non-existant runmode/page is accessed. Gives a nicer error message, when typing a wrong url 
sub AUTOLOAD : Runmode { 
    my $self = shift; 
    my $rm = shift; 
    my $template = $self->load_tmpl("default.html"); 
    $template->param(NAME => 'AUTOLOAD'); 
    $template->param(MESSAGE => "<p>Error: could not find run mode \'$rm\'<br>\n"); 
    $template->param(MYURL => $self->query->url); 
    return $template->output; 
} 

1; 

アップデート2

私は今mustLogin実行モードが呼び出されたとき$self->authen->usernameは常にundefに設定されていることを取得しています。これは、複数のユーザーがログインできることを意味します。

私は

open F, ">/tmp/debug"; 
    print F Dumper $self->authen->username; 
    close F; 

問題が発生

が挿入されています。

$self->cfg('SESSIONS_DIR')正しいパスを返します。

なぜ$self->authen->usernameundefに設定されているのですか?mustLoginが実行されていますか?

sub teardown { 
    my $self = shift; 

    $self->param('found_a_user', 0); 

    CGI::Session->find(
     "driver:File;serializer:yaml", 
     sub { my_subroutine($self, @_)}, 
     {Directory => $self->cfg('SESSIONS_DIR'), UMask => 0600,}, 
    ); 

    open F, ">/tmp/debug"; 
    print F Dumper $self->authen->username; 
    close F; 

    # get state of can_login file 
    open my $fh, '+<', 'can_login.yaml'; 
    flock($fh, LOCK_EX) or die "couldn't get lock: $!\n"; 
    my $c = YAML::Syck::LoadFile($fh); 

    if ($self->param('found_a_user')) { 
     # found a logged in user with an unexpired session 
     $c->{can_login} = 0; 
    } else { 
     # did NOT find any logged in users 
     $c->{can_login} = 1; 
    } 

    # write 
    my $yaml = YAML::Syck::Dump($c); 
    $YAML::Syck::ImplicitUnicode = 1; 
    seek $fh,0, SEEK_SET; # seek back to the beginning of file 
    print $fh $yaml . "---\n"; 
    close $fh; 
} 

sub my_subroutine { 
    my $self = shift; 
    my ($session) = @_; # I don't actually need this for anything here 

    if ($self->authen->username) { 
    $self->param('found_a_user', 1); 
    } 

} 
+0

他のユーザーがログインするには、事前にログアウトする必要がありますか? –

+0

@Alexandr Ciornii:これは今問題です。セッションがタイムアウトすると、 '$ can_login'の状態を保持しているファイルがTRUEに変更されていないため、誰もログインできなくなります。アクティブなセッションがない場合、 '$ can_login'をどのようにTRUEに設定できるか考えていますか? –

+0

最終アクセス時刻とユーザー名をDB /ファイルに格納します。ユーザーがアクセスするときは、同じユーザーかどうかを確認します。別のユーザーで時間が経過している場合は、ログインさせてください。 –

答えて

2

一度に1人のユーザーですか?それはかなり奇妙な要件です。私は前にそのようなことをする必要はなかった。ここではこれについて説明します: 'can_login'のようなバイナリ状態変数が必要です。これをファイルまたはデータベースに格納することができます。それを 'true'に初期化し、ユーザーが正常にログインするとすぐに 'false'に切り替えます。これを行う方法? $ self-> authen-> config()の中で、POST_LOGIN_CALLBACKの値を定義します。これは、 'can_login'値をチェックするコード参照を指す必要があります。ユーザーが認証されていて、 'can_login'がtrueの場合、他のログインを防止するために 'can_login'をfalseに切り替えます。ユーザーが認証され、 'can_login'がfalseの場合は、$ self-> authen-> logoutを呼び出してログアウトし、「1つしかありません!」というページにリダイレクトします。または何でも。また、ログイン情報を保持しているセッションでタイムアウト値を作成するようにして、誰かがログアウトするのを忘れてしまっているため、誰もが誤ってロックアウトしないようにしてください。ファイルに 'can_login'値を格納する場合、競合状態を避けるためにファイルロックを実装する必要があります。データベースに格納する場合は、適切なデータベースハンドルをコールバックコードrefに渡す方法を理解する必要があります(setup()メソッドのグローバル変数に貼り付けることもできます)。あなたのログアウトrunmodeは、いつものようにユーザーをログアウトさせ、PLUSは 'can_login'値をtrueに戻します。期限切れの古いセッションを処理するコードでもこれを行う必要があります。別のプロセス(5分ごとに実行されるcronジョブのような)でこれを行うこともできます。古いセッションを期限切れにし、アクティブなログインをチェックし、存在しなければ 'can_login'が真であることを確認します。セッションの期限切れ処理する方法について

UPDATE

は、あなたがあなたのcgiapp_init()メソッド内でこのような何かを持っていると仮定してみましょう:

$self->session_config(
    CGI_SESSION_OPTIONS => [ 
    "driver:File;serializer:yaml", 
    $self->query, 
    {Directory => $self->cfg('SESSIONS_DIR'), UMask => 0600,}, 
    ], 

    DEFAULT_EXPIRY => '+1h', 
    COOKIE_PARAMS => { 
    -path  => '/', 
    -httponly => 1,  # help avoid XSS attacks 
    }, 
); 

その後、通常次のようなティアダウン方法をお勧めします:

sub teardown { 
    my $self = shift; 

    # purge old sessions 
    CGI::Session->find(
    "driver:File;serializer:yaml", 
    sub {}, 
    {Directory => $self->cfg('SESSIONS_DIR'), UMask => 0600,}, 
); 
} 

teardown()メソッドは、すべての実行モードの最後に呼び出されます。この場合、古いセッションは期限切れになります(詳細はCGI :: Sessionのドキュメントを参照 - 「find」メソッドの下のセクションを見てください)。一般的な形式は以下です:

find($dsn, \&code, \%dsn_args); 

$のDSNと\%のdsn_argsはあなたのセッションの設定を持っているものは何でも一致している必要があります - 私は、例えば、鉱山を示した理由です。 find()は各セッションのload()を自動的に呼び出すため、何もする必要はありません。すでに有効期限が切れているものは自動的に削除されます。しかし、これを使ってログインしているユーザーを確認することができます。 my_subroutineが方法ではありませんので、私は追加の引数として$自己を渡すために持っていることを

sub teardown { 
    my $self = shift; 

    $self->param('found_a_user',0); 

    CGI::Session->find(
    "driver:File;serializer:yaml", 
    sub { my_subroutine($self, @_) }, 
    {Directory => $self->cfg('SESSIONS_DIR'), UMask => 0600,}, 
); 

    if ($self->param('found_a_user')) { 
    # found a logged in user with an unexpired session: set $can_login=0 here 
    } else { 
    # did NOT find any logged in users - set $can_login=1 here 
    } 

} 

sub my_subroutine { 
    my $self = shift; 
    my ($session) = @_; # I don't actually need this for anything here 

    if ($self->authen->username) { 
    $self->param('found_a_user',1); 
    } 

} 

注:あなたはこのような何かを行う必要があるでしょう。 私はauthen-> usernameにアクセスする必要があります。これは期限切れのセッションでログインしているユーザーがいる場合にのみ当てはまります。これは、セッションごとに呼び出され、古いセッションは削除されます。 'found_a_user'パラメータが1に設定されている場合、少なくとも1人のアクティブユーザが見つかり、必要に応じて$ can_login変数を更新できます。

+0

ありがとうございました!私はそれに慣れるでしょう。 '$ can_login'で状態を覚えておくのではなく、ログインランモードで' if($#users> 1){$ self-> authen-> logout;} 'を実行することができます。ログインしたユーザーの数を聞くことができれば、そのようなことはありますか? –

+1

私が知る限り、認証モジュールには、現在ログインしているユーザー数をカウントするものは何もありません。必要な場合は、同様の方法で実装することができます。単に$ user_count変数を$ can_loginブール値。ログイン/ログアウトメソッド内のカウントを更新してください(期限切れのセッションによるログアウトについては注意してください!)もちろん、あなたが望む元の実装が0または1以外の値になることはありません。それをもっと簡単にするための "カウント"メソッドを期待して、申し訳ありません! – scorpio17

+0

あなたのファイルロックソリューションは素晴らしい作品です。更新された機能でポストを更新しました。私は保護されたページがセッションが期限切れになったかどうかを調べる方法を理解できません。セッションがなければ、 '$ can_login'の状態を変更しなければならないと思いますか? –

関連する問題