Эх сурвалжийг харах

Clearly messing something up here

Andy Baugh 3 жил өмнө
parent
commit
f51268bd0d

+ 3 - 3
lib/Trog/Auth.pm

@@ -60,7 +60,7 @@ Returns a session ID, or blank string in the event the user does not exist or in
 
 =cut
 
-sub mksession ($type,$user,$pass) {
+sub mksession ($user,$pass) {
     my $dbh = _dbh();
     my $records = $dbh->selectall_arrayref("SELECT salt FROM user WHERE name = ?", { Slice => {} }, $user);
     return '' unless ref $records eq 'ARRAY' && @$records;
@@ -95,10 +95,10 @@ Returns True or False (likely false when user already exists).
 =cut
 
 sub useradd ($user, $pass, $acls) {
-    my $dbh = _dbh();
+    my $dbh  = _dbh();
     my $salt = create_uuid();
     my $hash = sha256($pass.$salt);
-    my $res =  $dbh->do("INSERT OR REPLACE INTO user (name,salt,hash) VALUES (?,?,?)", undef, $user, $salt, $hash);
+    my $res  = $dbh->do("INSERT OR REPLACE INTO user (name,salt,hash) VALUES (?,?,?)", undef, $user, $salt, $hash);
     return unless $res && ref $acls eq 'ARRAY';
 
     #XXX this is clearly not normalized with an ACL mapping table, will be an issue with large number of users

+ 1 - 1
lib/Trog/Authz.pm

@@ -9,7 +9,7 @@ use feature qw{signatures state};
 use constant 'valid_modules'   => [ 'Default', 'Matrix' ];
 
 sub do_auth_for ($module, $params) {
-    die "Invalid authorization class" if !grep { $module eq $_ } @{$class->valid_modules()};
+    die "Invalid authorization class" if !grep { $module eq $_ } @{__PACKAGE__->valid_modules()};
     my $class = "Trog::Authz::$module";
     eval "require $class";
     return $class->new($params);

+ 20 - 1
lib/Trog/Authz/Base.pm

@@ -18,9 +18,28 @@ sub do_auth {
     die "Implemented in subclass";
 }
 
-sub failed {
+sub failed ($self, $failed) {
+    $self->{'failed'} = $failed if defined($failed);
     $self->{'failed'} //= -1;
     return $self->{'failed'};
 }
 
+sub headers ($self, @headers) {
+    $self->{'headers'} = \@headers if @headers;
+    return @{$self->{'headers'}};
+}
+
+sub handle_cookie ($self, $cookie) {
+    if ($cookie) {
+        # TODO secure / sameSite cookie to kill csrf, maybe do rememberme with Expires=~0
+        my $secure = '';
+        $secure = '; Secure' if $self->{'params'}->{scheme} eq 'https';
+        $self->headers(
+            "Set-Cookie" => "tcmslogin=$cookie; HttpOnly; SameSite=Strict$secure",
+        );
+        $self->failed(0);
+    }
+    return;
+}
+
 1;

+ 85 - 14
lib/Trog/Authz/Default.pm

@@ -8,31 +8,102 @@ use feature qw{signatures state};
 
 use parent Trog::Authz::Base;
 
+use File::Touch ();
+use Trog::Auth  ();
+use Trog::Data  ();
+
 use constant 'required_params' => [ 'username', 'password' ];
 
 sub do_auth ($self) {
     if (!$self->{'hasusers'}) {
         # Make the first user
-        Trog::Auth::useradd($self->{'params'}->{username}, $self->{'params'}->{password}, ['admin'] );
+        Trog::Auth::useradd( $self->{'params'}{username}, $self->{'params'}{password}, ['admin'] );
         # Add a stub user page and the initial series.
-        my $dat = Trog::Data->new($conf);
-        _setup_initial_db($dat,$self->{'params'}->{username});
+        _setup_initial_db($self->{'params'}{username});
         # Ensure we stop registering new users
         File::Touch::touch("config/has_users");
     }
 
-    $self->{failed} = 1;
-    my $cookie = Trog::Auth::mksession( 'Default', $self->{'params'}->{username}, $self->{'params'}->{password});
-    if ($cookie) {
-        # TODO secure / sameSite cookie to kill csrf, maybe do rememberme with Expires=~0
-        my $secure = '';
-        $secure = '; Secure' if $self->{'params'}->{scheme} eq 'https';
-        @headers = (
-            "Set-Cookie" => "tcmslogin=$cookie; HttpOnly; SameSite=Strict$secure",
-        );
-        $self->{failed} = 0;
-    }
+    $self->failed(1);
+    my @acls = Trog::Auth::acls4user($self->{'params'}{'username'});
+    return $self if !@acls; # A user without ACLs can't really do anything, so why login?
+    return if grep { $_ eq 'extAuthUser' } @acls; # Return if ext auth user yet on this path (shenanigans)
+
+    my $cookie = Trog::Auth::mksession( $self->{'params'}{username}, $self->{'params'}{password} );
+    $self->handle_cookie($cookie);
     return $self;
 }
 
+sub _setup_initial_db ($user) {
+   my $dat = Trog::Data->new(Trog::Config::get());
+   $dat->add(
+        {
+            "aclname"    => "series",
+            "acls"       => [],
+            "callback"   => "Trog::Routes::HTML::series",
+            method       => 'GET',
+            "data"       => "Series",
+            "href"       => "/series",
+            "local_href" => "/series",
+            "preview"    => "/img/sys/testpattern.jpg",
+            "tags"       => [qw{series topbar}],
+            visibility   => 'public',
+            "title"      => "Series",
+            user         => $user,
+            form         => 'series.tx',
+            child_form   => 'series.tx',
+            aliases      => [],
+        },
+        {
+            "aclname"    => "about",
+            "acls"       => [],
+            "callback"   => "Trog::Routes::HTML::series",
+            method       => 'GET',
+            "data"       => "About",
+            "href"       => "/about",
+            "local_href" => "/about",
+            "preview"    => "/img/sys/testpattern.jpg",
+            "tags"       => [qw{series topbar public}],
+            visibility   => 'public',
+            "title"      => "About",
+            user         => $user,
+            form         => 'series.tx',
+            child_form   => 'profile.tx',
+            aliases      => [],
+        },
+        {
+            "aclname"      => "config",
+            acls           => [],
+            "callback"     => "Trog::Routes::HTML::config",
+            'method'       => 'GET',
+            "content_type" => "text/html",
+            "data"         => "Config",
+            "href"         => "/config",
+            "local_href"   => "/config",
+            "preview"      => "/img/sys/testpattern.jpg",
+            "tags"         => [qw{admin}],
+            visibility     => 'private',
+            "title"        => "Configure tCMS",
+            user           => $user,
+            aliases        => [],
+        },
+        {
+            title      => $user,
+            data       => 'Default user',
+            preview    => '/img/avatar/humm.gif',
+            wallpaper  => '/img/sys/testpattern.jpg',
+            tags       => ['about'],
+            visibility => 'public',
+            acls       => ['admin'],
+            local_href => "/users/$user",
+            callback   => "Trog::Routes::HTML::users",
+            method     => 'GET',
+            user       => $user,
+            form       => 'profile.tx',
+            aliases    => [],
+        },
+    );
+    return;
+}
+
 1;

+ 69 - 2
lib/Trog/Authz/Matrix.pm

@@ -8,13 +8,80 @@ use feature qw{signatures state};
 
 use parent Trog::Authz::Base;
 
+use Trog::Auth ();
+use HTTP::Tiny ();
+
 use constant 'required_params' => [ 'extAuthData' ];
 
 sub do_auth ($self) {
     die "Please setup an admin user first" if !$self->{'params'}{'hasuers'};
+    $self->failed(1);
+
+    require JSON::XS;
+    my $decoded;
+    eval { $decoded = JSON::XS::decode_json($self->{'params'}{'extAuthData'}); };
+    return $self if !$decoded;
+
+    # XXX TODO potential security/DOS issue -- How does one prevent spoofing on this end?
+    # For example, user POSTs the json blob with the right param names, etc. but no
+    # actual auth gating -- Suddenly you have a user and session more or less out of
+    # whole cloth with no auth ever done. Only thing I can think of to prevent this
+    # is more or less "after the fact" via calling `isSignedIn()` as part of pageload
+    # when you are a matrix user, invalidating the session if not.
+    # I'm sure you can already tell the hole in this perfect plan -- "just disable js".
+    # The only real way I can think of for now to prevent abuse here is to do a check
+    # on the backend using the access token and user just to do some kind of ping check.
+    # That said this still just makes it "harder" to pull off bullshit. Only way to be
+    # 100% sure we are talking to a matrix server would be to do it all on the backend.
+    # That said, this is probably "good enough" for now.
+    my $ping_url = $decoded->{'well_known'}{'m.homeserver'}{'base_url'};
+    $ping_url .= "/_matrix/client/r0/presence/$decoded->{'user_id'}/status";
+    $ping_url .= "?access_token=$decoded->{'access_token'}";
+    my $resp = HTTP::Tiny->new->get($ping_url);
+    return $self unless $resp->{'success'};
+
+    # I'm using ACLs here as a proxy for the user existing.
+    # That may? be a bad assumption. If so I need to make another sub for getting this.
+    my @acls = Trog::Auth::acls4user($decoded->{user_id});
+    if(!@acls) {
+
+        # Create the user XXX TODO -- Have a config param for "extra" ACLs the admin
+        # can assign in the UI/Config in order to give them other "default" ACLs like
+        # access to private content or ability to comment on articles, etc.
+        my $cfg = Trog::Config::get();
+        Trog::Auth::useradd(
+            $decoded->{user_id},
+            'Matrix', # Never used here, so just put in the ext auth provider name.
+            [ 'extAuthUser' ], # This ACL is important, and should be non-removable.
+        );
+        my $dat = Trog::Data->new($cfg);
+        $dat->add(
+            {
+                title      => $decoded->{user_id},
+                data       => $decoded->{displayname},
+                preview    => $decoded->{avatar_content},
+                wallpaper  => '/img/sys/testpattern.jpg',
+                tags       => ['about'],
+                visibility => 'public',
+                acls       => ['admin'],
+                local_href => "/users/$decoded->{user_id}",
+                callback   => "Trog::Routes::HTML::users",
+                method     => 'GET',
+                user       => $decoded->{user_id},
+                form       => 'profile.tx',
+                aliases    => [],
+            },
+        );
+
+    }
+
+    # Check if banned
+    return $self if grep { $_ eq 'banned' } @acls;
+
+    my $cookie = Trog::Auth::mksession( $decoded->{user_id}, 'Matrix' );
+    $self->handle_cookie($cookie);
 
-    # TODO: Parse json from params->extAuthData, figure it out from there
-    $self->{'failed'} = 1;
+    $self->failed(0);
     return $self;
 }
 

+ 3 - 1
lib/Trog/Config.pm

@@ -17,8 +17,10 @@ Returns a configuration object that will be used by server.psgi, the data model
 
 our $home_cfg = "config/main.cfg";
 
+# Cache it in memory since we do that a lot elsewhere.
+my $cf;
 sub get {
-    my $cf;
+    return $cf if $cf;
     $cf = Config::Simple->new($home_cfg) if -f $home_cfg;
     return $cf if $cf;
     $cf = Config::Simple->new('config/default.cfg');

+ 10 - 79
lib/Trog/Routes/HTML.pm

@@ -405,91 +405,22 @@ sub login ($query, $render_cb) {
         $auth_module = ucfirst($query->{'extAuthProvider'}) if($query->{'extAuthProvider'});
         require Trog::Authz;
         my $auth_obj = Trog::Authz::do_auth_for( $auth_module, $query );
-        $failed = $auth_obj->failed();
+        $failed  = $auth_obj->failed();
+        @headers = $auth_obj->headers();
     }
 
     return $render_cb->('login.tx', {
-        title         => 'tCMS 2 ~ Login',
-        to            => $query->{to},
-        failure => int( $query->{failed} ),
-        message => int( $query->{failed} ) < 1 ? "Login Successful, Redirecting..." : "Login Failed.",
-        btnmsg        => $btnmsg,
-        hasusers      => $query->{'hasusers'} ? 1 : 0,
-        stylesheets   => _build_themed_styles('login.css'),
-        theme_dir     => $td,
+        title       => 'tCMS 2 ~ Login',
+        to          => $query->{to},
+        failure     => int( $failed ),
+        message     => int( $failed ) == 0 ? "Login Successful, Redirecting..." : "Login Failed.",
+        btnmsg      => $btnmsg,
+        hasusers    => $query->{'hasusers'} ? 1 : 0,
+        stylesheets => _build_themed_styles('login.css'),
+        theme_dir   => $td,
     }, @headers);
 }
 
-sub _setup_initial_db ($dat, $user) {
-   $dat->add(
-        {
-            "aclname"    => "series",
-            "acls"       => [],
-            "callback"   => "Trog::Routes::HTML::series",
-            method       => 'GET',
-            "data"       => "Series",
-            "href"       => "/series",
-            "local_href" => "/series",
-            "preview"    => "/img/sys/testpattern.jpg",
-            "tags"       => [qw{series topbar}],
-            visibility   => 'public',
-            "title"      => "Series",
-            user         => $user,
-            form         => 'series.tx',
-            child_form   => 'series.tx',
-            aliases      => [],
-        },
-        {
-            "aclname"    => "about",
-            "acls"       => [],
-            "callback"   => "Trog::Routes::HTML::series",
-            method       => 'GET',
-            "data"       => "About",
-            "href"       => "/about",
-            "local_href" => "/about",
-            "preview"    => "/img/sys/testpattern.jpg",
-            "tags"       => [qw{series topbar public}],
-            visibility   => 'public',
-            "title"      => "About",
-            user         => $user,
-            form         => 'series.tx',
-            child_form   => 'profile.tx',
-            aliases      => [],
-        },
-        {
-            "aclname"      => "config",
-            acls           => [],
-            "callback"     => "Trog::Routes::HTML::config",
-            'method'       => 'GET',
-            "content_type" => "text/html",
-            "data"         => "Config",
-            "href"         => "/config",
-            "local_href"   => "/config",
-            "preview"      => "/img/sys/testpattern.jpg",
-            "tags"         => [qw{admin}],
-            visibility     => 'private',
-            "title"        => "Configure tCMS",
-            user           => $user,
-            aliases        => [],
-        },
-        {
-            title      => $user,
-            data       => 'Default user',
-            preview    => '/img/avatar/humm.gif',
-            wallpaper  => '/img/sys/testpattern.jpg',
-            tags       => ['about'],
-            visibility => 'public',
-            acls       => ['admin'],
-            local_href => "/users/$user",
-            callback   => "Trog::Routes::HTML::users",
-            method     => 'GET',
-            user       => $user,
-            form       => 'profile.tx',
-            aliases    => [],
-        },
-    );
-}
-
 =head2 logout
 
 Deletes your users' session and opens the index.

+ 1 - 1
service-files/systemd.unit

@@ -5,5 +5,5 @@ Description=tCMS
 WantedBy=default.target
 
 [Service]
-ExecStart=starman -p __PORT__ __REPLACEME__/www/server.psgi
+ExecStart=starman --error-log __REPLACEME__/tCMS.log -p __PORT__ __REPLACEME__/www/server.psgi
 WorkingDirectory= __REPLACEME__/

+ 0 - 1
www/templates/login.tx

@@ -35,7 +35,6 @@
 		<matrix-signin id="signin"></matrix-signin>
 		<script>
 		  var hasusers = <: $hasusers :>;
-		  console.log("Has Users? " + hasusers);
 		  var loginBits = document.getElementById("matrixLogin");
 		  if(hasusers) {
 			  loginBits.style = "display:block";