Suche
Suche Menü

Sichere Downloads mittels .htaccess und FrontEndUsers (securefile1.1)

Um sichere Downloads (nur für authentifizierte Benutzer) zu ermöglichen gibt es verschiedene Wege. Ein Download-Manager-Modul wäre die eine Möglichkeit. Seit längerem gibt es für CMS Made Simple das Plugin securefile. Ich habe das Plugin nun ein wenig weiterentwickelt und den Einsatz vereinfacht.

Voraussetzung ist das FrontEndUser Modul welches den Zugriff auf die Dateien steuert.

Als erstes muss der Ordner der geschützt werden soll mit folgendem .htaccess ausgeschattet werden:

Options +FollowSymLinks
RewriteEngine on
RewriteBase /
RewriteRule ^(.+)$ download.php?fgroup=3&url=uploads/secfolder/$1 [L]

Dabei müssen folgende Parameter indivduell angepasst werden:

  • fgroup (zwingend): ID der FEU-Gruppe. Mehrere Gruppen können mit Komma getrennt werden
    Beispiel: fgroup=2 oder fgroup=2,3,8
  • url (zwingend): Pfad zum Ordner. Der Ordner muss in /uploads liegen.
    Beispiel: url=uploads/secfolder
  • pageid (optional): ID der Seite auf welche bei einem Fehler weitergeleitet werden soll
    Beispiel: page_id=95
  • pagealias (optional): Alias der Seite auf welche bei einem Fehler weitergeleitet werden soll.
    Beispiel: pagealias=fehlerseite

Zuletzt muss die download.php im CMSms Root (z.B. www.deineseite.com/download.php) abgelegt werden.
Hier die download.php:

<?php
# #####################################################################
#    coded for #CMS - CMS Made Simple
#    copyright (c) 2011 by BlatterTech Informatik (www.blattertech.ch)
#    Version 1.0 - 26.06.2007 inital release
#    Version 1.1 - 13.06.2011 modified by BlatterTech Informatik
#    Version 1.2 - 18.03.2012 modified by BlatterTech Informatik (Bugfixes, PHP5.3 ready)
# Version 1.3 - 08.08.2014 modified by BlatterTech Informatik (Bugfixes echo readfile --> $bytes = readfile)
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
# #####################################################################

require_once(dirname(__FILE__)."/include.php");

function redirect403 ($code = 403) {
    $gCms = cmsms();
    if (isset($_GET['pagealias']) and $_GET['pagealias'] != "" and $code != 404)  {
        $manager = $gCms->GetHierarchyManager();
        $node = $manager->sureGetNodeByAlias($_GET['pagealias']);
        if (isset($node)) {
            $content =& $node->GetContent();
            if ($content !== FALSE && is_object($content) && $content->Active() && $content->HasUsableLink()) {
                $url = $content->GetUrl();
                @session_start();
                $_SESSION['securedl'] = $_GET['url'];
                $_SESSION['securegid'] = $_GET['fgroup'];
                header("Location: ".$url);
                exit;
            }
        }
    }
    if (isset($_GET['pageid']) and $_GET['pageid'] != "" and $code != 404) {
        $manager = $gCms->GetHierarchyManager();
        $node = $manager->getNodeById($_GET['pageid']);
        if (isset($node)) {
            $content =& $node->GetContent();
            if ($content !== FALSE && is_object($content) && $content->Active() && $content->HasUsableLink()) {
                $url = $content->GetUrl();
                @session_start();
                $_SESSION['securedl'] = $_GET['url'];
                $_SESSION['securegid'] = $_GET['fgroup'];
                header("Location: ".$url);
                exit;
            }
        }
    }

    if ($code == 404) {
        header("HTTP/1.0 404 Not Found");
        echo "
            <html>
                <head>
                <title>404 Not Found</title>
                </head>
            <body>
                <p>File not found</p>
            </body>
            </html>
        ";
    }
    else {
        header("HTTP/1.0 403 Forbidden");
        echo "
            <html>
                <head>
                <title>403 Forbidden</title>
                </head>
            <body>
                <p>You have no access. Please login.</p>
            </body>
            </html>
        ";
    }
    exit;
}

// Control if all params are available
if (!isset($_GET['url']) or !isset($_GET['fgroup'])) {
    redirect403();
}

$pathinfo = pathinfo($_GET['url']);
$filename = $pathinfo['basename'];

//security check (folder must be in /uploads)
if (strpos($pathinfo['dirname'],"uploads/") === FALSE) {
    redirect403();
}

define("SECURE_FOLDER", dirname(__FILE__).DIRECTORY_SEPARATOR.$pathinfo['dirname']);
define("LOG_DOWNLOADS", true); //creates a log file in secure folder
define("DELIVER_CHUNKED", true); //enable if you have trouble with large files

############ do not edit below unless you know what you're doing ###########
@ini_set('zlib.output_compression', 'Off');

function readfile_chunked($filename,$retbytes=true) {
    $chunksize = 1*(1024*1024); //how many bytes per chunk
    $buffer = '';
    $cnt =0;
    $handle = fopen($filename, 'rb');
    if ($handle === false) {
        return false;
    }
    while (!feof($handle)) {
        $buffer = fread($handle, $chunksize);
        echo $buffer;
        ob_flush();
        flush();
        if ($retbytes) {
            $cnt += strlen($buffer);
        }
    }
    $status = fclose($handle);
    if ($retbytes && $status) {
           return $cnt; // return num. bytes delivered like readfile() does.
    }
    return $status;
}

function btHasAccess($feugroups) {
  if (!$feugroups) return false;
  $feusers = cms_utils::get_module('FrontEndUsers');
  $user_id = $feusers->LoggedInId();
  $groups = $feusers->GetMemberGroupsArray($user_id);

  $gns = array();
  if($groups !== false ) {
      foreach( $groups as $gid ) {
          $gns[] = $gid['groupid'];
      }
  }

  $feuArray= explode(',',$feugroups);
  $retval = false;
  foreach ($feuArray as $feu) {
      if (in_array($feu,$gns)) return true;
  }
  return false;
}

function btGetUsername() {
    $feusers = cms_utils::get_module('FrontEndUsers');
    return $feusers->LoggedInName();
}

// check FEU access
if (btHasAccess($_GET['fgroup'])) {
    $username = btGetUserName();
}
else {
    redirect403();
}

// check file_exists
if (!file_exists(SECURE_FOLDER.DIRECTORY_SEPARATOR.$filename)){
    redirect403(404);
}

$browser = $_SERVER['HTTP_USER_AGENT'];

if (strpos("Mac", $browser) !== false or strpos("MSIE", $browser) !== false) {
    $typ = "application/octet-stream";
}
else {
    $typ = "application/x-download";
}

//headers
header('Pragma: public');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate'); // HTTP/1.1
header('Cache-Control: pre-check=0, post-check=0, max-age=0'); // HTTP/1.1
header('Content-Transfer-Encoding: none');
header("Accept-Ranges: bytes");
header("Content-Type: $typ");
header("Content-Disposition: attachment; filename="{$filename}";");

//deliver file
@set_time_limit(0); //make sure large files dont get timed out.
if (DELIVER_CHUNKED == true) {
    $bytes = @readfile_chunked(SECURE_FOLDER.DIRECTORY_SEPARATOR.$filename);
}
else {
    $bytes = @readfile(SECURE_FOLDER.DIRECTORY_SEPARATOR.$filename);
}
//log the download
if (LOG_DOWNLOADS == true) {
    $ip = $_SERVER['REMOTE_ADDR'];
    $host = gethostbyaddr($ip);
    if (empty($host)) { $host = "UNRESOLVED"; }
    $datenow = date("Y.m.d");
    $timenow = date("H:i:s");

    $logfile = SECURE_FOLDER.DIRECTORY_SEPARATOR.".download_log.csv";
    $logdata = "";
    if (!is_file($logfile)) // add title at first line
        $logdata .="Date;Time;Filename;Username;IP-Adress;Host;Browsern";
    $logdata .= $datenow.";".$timenow.";".$filename.";".$username.";".$ip.";".$host.";".$browser."n";
    @file_put_contents($logfile, $logdata, FILE_APPEND | LOCK_EX);
}
exit;

 

Securefile1.3 herunterladen

Selektives aktivieren von Debug Modus bei CMSms

Der Debugmodus von CMS Made Simple kann beim Entwickeln und/oder der Fehlersuche sehr praktisch sein. Es werden alle SQL Statements sowie die benötigten Zeiten angezeigt. Des weiteren werden Sicherheitshinweise angezeigt, wenn Module nicht korrekt konfiguriert sind.

Will man dies allerdings bei einer aktiven Seite in der Live-Umgebung machen, sollten die Besucher der Seite dies nicht sehen, da dies einerseits eine Sicherheitslücke sein könnte, andererseits weil dies dem Design der Seite nicht gerade zuträglich ist.

Mit folgender Anpassung in der config.php können nur eingeloggte Backendbenutzer die Debug-Meldungen sehen. In der config.php folgende Zeile ersetzen:

 $config['debug'] = false;

mit:

 if (isset($_GET['debug']) and isset($_SESSION['cms_admin_username'])) {
  $config['debug'] = true;
} else {
  $config['debug'] = false;
}

Um das Debugging zu aktivieren muss einfach in der Adresszeile ein ?debug=1 angehängt werden.
Beispiel: www.blattertech.ch/webdesign.html?debug=1. Dies funktioniert wie oben geschrieben nur wenn man als Benutzer im Backend angemeldet ist. Somit kann selektiv für eine Seite Debugging eingeschaltet werden ohne dass Veränderungen an der config.php vorgenommen werden müssen oder normale Webseitenbesucher etwas vom Debug-Modus sehen.

via i-do-this.com (Code PHP 5.3 fähig gemacht)

You can access debug mode by adding ?debug=1 to your URL in the browser address bar. If you already have options, append &debug=1 . This will only work if you have logged into the admin panel in the same session.

Even if someone stumbles on the ?debug=true trick, they won’t see anything, because they haven’t logged in.

Summary Texte kürzen in CMS Made Simple

Ein Summary (Vorschautext) darf je nach dem nicht mehr als eine bestimmte Anzahl Zeichen enthalten. Um ein Summary oder auch den Inhalt einer News, eines Blogartikel oder ähnlichem zu kürzen, gibt es zwei Plugins die dazu genutzt werden können.

summarize

summarize ist ein Modifier der direkt der Variable angehängt werden kann.

{$entry->summary|summarize}

Der Modifier ist mit 3 Parametern aufgebaut: (Text, Anzahl Worte, Endtag). Die Standardeinstellung ist 5 Worte und als Endtag «[…]». Er kann sowohl mit als auch ohne Paramter eingesetzt werden. Wie folgt kann man den Modifier individuell einsetzen:

{$entry->summary|summarize:5:"[...weiter...]"}

Die Ausgabe des folgenden Textes «Das ist eine grosse Sache mit CMS Made Simple» ist in dem Fall «Das ist eine grosse Sache […weiter…]». Zu beachten ist, dass summarize auch HTML Zeichen als Text wertet. Der Text wird durch alle Leerschläge getrennt und entsprechend gezählt.

truncate_better

truncate_better hingegen berücksichtigt HTML Tag und probiert diese wenn immer Möglich zu schliessen. Ein offenes <p> oder auch ein <a href  wird jeweils wieder geschlossen. Zudem werden diese Zeichen im Gegensatz zu summarize nicht in der Länge berücksichtigt.

{truncate_better text=$entry->summary truncate='160' add=" [...]"}

Modifier truncate_better

NaN hat eine modifier Version von truncate_better mit ein paar kleinen Fehlerkorrekturen erstellt.

{$entry->summary|truncate_better:'160':' [...]'}

Download modifier.truncate_better.php. Hier der Forenbeitrag dazu.

Smarty Array und foreach Schleifen

Mit Smarty werden die Daten in der Regel mit einem ein oder mehrstufen Array übergeben. Um ein Array auszugeben benutzt man in der Regel den foreach Befehl. In diesem Beitrag behandle ich die verschiedenen Möglichkeiten die man mit Smarty beim durchlaufen einer foreach Schleife

Erstes und letztes Element einer foreach Schleife

first ist TRUE wenn die aktuelle Iteration das erste Element ist und last ist TRUE wenn die aktuelle Iteration die letzte Element ist:

{foreach from=$array item=one name=oneitem}
 {if $smarty.foreach.oneitem.first} Beim ersten Element ausführen {/if}
 {if $smarty.foreach.oneitem.last} Beim letzen Element ausführen {/if}
{/foreach}
{foreachelse} Kein Datensatz gefunden {/foreachelse}

Index und Interations

Index gibt den aktuellen Index des Array beginnend mit Null aus. Interation hingegen startet immer bei 1 und wird danach bei jedem durchgang um 1 inkrementiert.

{* Dies ergibt den folgenden Output 0|1, 1|2, 2|3, ... etc *}
{foreach from=$array item=one name=oneitem}
 {$smarty.foreach.oneitem.index}|{$smarty.foreach.oneitem.iteration},
{/foreach}

foreachelse

foreachelse wird ausgeführt wenn keine Datensätze gefunden wurden. Meistens wird jedoch in einem Template bereits vor der foreach Schleife kontrolliert ob diese überhaupt Daten enthält.

{foreach from=$array item=one name=oneitem}
 Mach was
{/foreach}
{foreachelse} Kein Datensatz gefunden {/foreach}

continue; und break;

Die aus PHP bekannten continue; und break; sind standardmässig nicht in Smarty enthalten. Sie können durch das erstellen einer Compiler nachgerüstet werden. Dazu müssen folgende zwei Dateien erstellt werden:

<?php
// compiler.continue.php

function smarty_compiler_continue($contents, &$smarty) {
return 'continue;';
}

sowie

<?php
// compiler.break.php

function smarty_compiler_break($contents, &$smarty) {
return 'break;';
}

Diese beiden Dateien werden unter ./lib/smarty/plugins abgelegt. Danach können die beiden Funktionen mit {break} und {continue} in der foreach Schleife genutzt werden.

{foreach from=$array item=one name=oneitem}
{* Es wird direkt zum nächsten Element der Schleife gesprungen *}
{if $one.name == "Test"}{continue}{/if}
 {* Die Schleife wird abgebrochen *}
{if $one.name == "Test"}{break}{/if}
{/foreach}

Weitere Infos zur foreach Schleife gibt es in der Smarty Dokumentation.

ODBC unter Windows 2008 R2 64Bit einrichten

Letzhin musste ich unter Windows 2008 R2 64Bit Server eine ODBC Verbindung einrichten. Nach dem einrichten der ODBC Verbindung war diese jedoch im Programm das die Verbindung nutzt nicht sichtbar.

Der Fehler ist ein wenig tricky. Win2k8R2 legt nur in der 64Bit Umgebung eine ODBC Verbindung ein. Ein Programm das 32Bit läuft, kann auf diese Verbindung nicht zugreifen. Damit für ein 32Bit Programm eine ODBC Verbindung eingerichtet werden kann, muss das Tool dazu manuell gestartet werden. Folgende Datei ist dazu zu öffnen:

C:WindowsSysWOW64odbcad32.exe

32Bit ODBC Verbindung einrichten

Nun wie gewohnt die ODBC Verbindung erstellen. Danach steht diese Verbindung im 32Bit Programm zur Verfügung.

ODBC Verbindung Einrichten

SitemapMadeSimple um CGBlog und News erweitern

Mit Sitemap Made Simple lässt sich einfach ein sitemap.xml erstellen, welches durch die Suchmaschinen indexiert werden kann. Von Haus aus unterstützt Sitemap Made Simple nur Inhaltsseiten. Man kann jedoch das Template des Sitemaps problemlos um weiteres ergänzen.

CGBlog integrieren

Um CGBlog in das sitemap.xml zu integrieren, muss folgender Code dem Sitemap Template angehängt werden:

{capture assign='junk'}{CGBlog number='1000'}{/capture}
{foreach from=$items item=entry}
{assign var=blogsm value=$entry->detail_url|replace:'/15/':'/57/'|replace:'http:/57':'http:/'}
<url>
<loc>{$blogsm}</loc>
<lastmod>{$entry->postdate|date_format:"%Y-%m-%d"}</lastmod>
<priority>{$page->priority}</priority>
<changefreq>{$page->frequency}</changefreq>
</url>
{/foreach}

News integrieren

Auch News lässt sich einfach darin integrieren:

{capture assign='junk'}{news number='1000'}{/capture}
{foreach from=$items item=entry}
{assign var=utmpNEWS value=$entry->moreurl|replace:'//':'/1/'|replace:'http:/1':'http:/'}
<url>
<loc>{$utmpNEWS}</loc>
{if $entry->formatpostdate}<lastmod>{$entry->formatpostdate|date_format:"%Y-%m-%d"}</lastmod>{/if}
<priority>{$page->priority}</priority>
<changefreq>{$page->frequency}</changefreq>
</url>
{/foreach}

Seiten vom Sitemap auschliessen

Hat man z.B. mit AdvancedContent und FrontEndUsers verschiedene Seiten geschützt, werden diese dennoch im Sitemap angezeigt. Um dies zu verhindern, kann man folgendes ins Template einbauen:

{if $page->url|strpos:"/intern" == FALSE and  $page->url|strpos:"/test" == FALSE}
<url>
<loc>{$page->url}</loc>
<lastmod>{$page->date|date_format:"%Y-%m-%d"}</lastmod>
<priority>{$page->priority}</priority>
<changefreq>{$page->frequency}</changefreq>
</url>
{/if}

Mit der Abfrage in der ersten Zeile werden alle Seiten welche in der Url */intern* oder */test* aufweisen vom Sitemap ausgeschlossen. Wichtig ist der schliessende {literal}{/if}{/literal} Tag am Schluss.

Inspiration dieses Beitrags durch @uniqu3 und @jeremyBass

1.6er Reihe von CMS Made Simple wird nicht mehr unterstützt

Aufgrund der Tatsache, dass PHP 5.2.x bereits 2006 veröffentlicht wurde und nur noch eine geringe Zahl von Anwendern CMSMS 1.6.x verwenden, wird die 1.6er Reihe von CMSMS nicht länger unterstützt.

Folglich wird es auch KEINE Sicherheits-Updates mehr geben.

Jeder CMSMS-Anwender mit einer Version < 1.7 sollte sich daher demnächst Gedanken über eine Aktualisierung machen oder aber seinen Hoster auffordern, auf eine aktuelle (durch CMSMS unterstützte = minimal 5.2.4) PHP-Version zu aktualisieren.

http://forum.cmsmadesimple.org/viewtopi … =1&t=54788

(Übersetzung durch Cyberman)

FrontEndUser Authentifizierung in eigenen Scripts verwenden

Folgenden Schnippsel habe ich mir letzhin geschrieben um in Scripts FEU Access zu kontrollieren

function btHasAccess($feugroups) {
    
    if (!$feugroups) return false;
    
    // FEU Modul laden
    $feusers = cms_utils::get_module('FrontEndUsers');
    $user_id = $feusers->LoggedInId();
    
    // Alle Gruppen einlesen zu welchen der eingeloggte Benutzer gehört
    $groups = $feusers->GetMemberGroupsArray($user_id);
    
    // Groups in Array schreiben
    $gns = array();
    if($groups !== false ) {
        foreach( $groups as $gid ) {
            $gns[] = $gid['groupid'];
        }
    }
    
    // Kontrolle ob User Access hat
    $feuArray= explode(',',$feugroups);
    $retval = false;
    foreach ($feuArray as $feu) {
        if (in_array($feu,$gns)) return true;
    }
    return false;
}
 

Das Script liest die Gruppen des eingeloggten Users aus. Kontrolliert ob der Benutzer Access hat wird mit

btHasAccess("1,3,4")

Die Zahlen in der Funktion stellen die ID’s der FEU-Benutzergruppen dar, welche Zugriff haben. Die Funktion liefert ein true oder ein false zurück.