Niedawno trafiłem na ciekawy problem w mod_rewrite - by przekierowywać użytkowników logujących się jednym z modułów mod_auth_basic do dedykowanych im katalogów, równocześnie blokując dostęp do katalogów innych użytkowników. Nie brzmi to jakoś strasznie ale problem okazał się być całkiem nietrywialnym. Teoretyczne rozwiązanie sprowadzało się do wyszukania loginu użytkownika ze ścieżki URI i porównania z nazwą użytkownika ze zmiennej %{REMOTE_USER} - jeśli wartości się różnią to Forbidden. Ale szybko okazało się że w RewriteCond zmienne z dopasowań można podstawiać tylko w pierwszym parametrze i że o ile można RewriteCond’y połączyć wyrażeniami logicznymi typu AND/OR to nie ma możliwości porównania czy dopasowania z kolejnych RewriteCond’ów są identyczne. Po kilku dniach szperania w dokumentacji i różnych tutorialach udało mi się trafić na jedną wartościową wskazówkę ale tej stronki już nie ma, więc opiszę problem dla potomnych.

Założenia są takie:

  • mamy vhost’a który udostępnia wszystkie foldery użytkowników,
  • każdy użytkownik posiada folder o nazwie dokładnie takiej samej jak jego login,
  • użytkownik po zalogowaniu ma być przekierowany do swojego folderu i przy próbie przejścia do folderów innych użytkowników albo nawracamy go do jego folderu/albo dajemy forbidden.

Konfiguracja vhost’a

Poniżej podstawowa konfiguracja vhost’a:


<VirtualHost *:80>
ServerName files.example.com
ServerAlias www.files.example.com
DocumentRoot /var/www/files
ErrorLog ${APACHE_LOG_DIR}/error.log
LogLevel warn
CustomLog ${APACHE_LOG_DIR}/access.log combined
<Directory /var/www/files>
 AllowOverride none
 AuthType basic
 AuthName "Zaloguj sie"
 AuthUserFile /etc/apache2/passwd
 Require valid-user
</Directory>
</VirtualHost>

Rewrite’y

I najciekawsza część czyli rewrite’y. Zaczynamy od przekierowania użytkownika do jego folderu:

RewriteRule ^$ /%{REMOTE_USER} [R,L]

Powyższy rewrite sprawdza czy próbujemy wejść do głównego katalogu, jeśli tak to przekierowujemy do katalogu użytkownika.

To teraz magia, której długo szukałem 😃


RewriteCond %{REMOTE_USER} ^(.+)
RewriteCond %1:$1 !^([^:]+):\1$
RewriteRule ^([^/]+)/ - [F,L]

Już wyjaśniam co to robi - zacznę od przypomnienia że pomimo takiego zapisu w konfiguracji reguły są przetwarzane nieco inaczej: Apache najpierw sprawdza czy dany URI pasuje do wyrażenia w RewriteRule, a dopiero gdy tak jest sprawdzane są warunki w RewriteCond. Czyli RewriteRule dopasowuje pierwszą część URI aż do znaku ukośnika / i zapamiętuje w zmiennej $1. Dopiero teraz RewriteCond dopasowuje i zapamiętuje login użytkownika w zmiennej %1 (tak rule zapamiętuje w zmiennych z $, cond w zmiennych z %). Teraz gdy mamy już zapamiętane loginy z URI i zmiennej to możemy je zapisać obok siebie w kolejnym RewriteCond oddzielając znakiem który w loginie wystąpić nie powinien (np. dwukropkiem) - $1:%1. Teraz dopasowujemy pierwszą część “sklejki”, czyli  ^([^:]+): i zaraz potem wymagamy by pojawiła się ta sama wartość przez wsteczną referencję \1$ - to porównywane jest z pierwszym parametrem cond’a. To dopasowanie jest prawdziwe gdy użytkownik loguje się prawidłowo, więc negujemy je stawiając ! na początku regexp’a, by każde błędne logowanie powodowało wywołanie RewriteRule, czyli Forbidden.

Pogmatwane? Więc teraz na przykładzie:

  • błędne logowanie (bo prostsze):
    login: roman
    uri: zbyszek/
    po dopasowaniu w $1 mamy zbyszek, a w %1 mamy roman, więc $1:%1 to zbyszek:roman, ostatni cond sprawdza czy zbyszek:roman różni się od zbyszek:zbyszek - a skoro tak to blokujemy dostęp,
  • dobre logowanie:
    login: roman
    uri: roman/
    po dopasowaniu w $1 i %1 mamy roman i sprawdzamy czy $1:%1 jest zgodne z roman:roman, a jest więc po negacji nie blokujemy dostępu. Skoro dostępu nie blokujemy to roman może dostać się do swoich plików.

Finalna konfiguracja

Zostało przedstawienie całościowo konfiguracji:


<VirtualHost *:80>
ServerName files.example.com
ServerAlias www.files.example.com
DocumentRoot /var/www/files
ErrorLog ${APACHE_LOG_DIR}/error.log
LogLevel warn
CustomLog ${APACHE_LOG_DIR}/access.log combined
<Directory /var/www/files>
 AllowOverride none
 AuthType basic
 AuthName "Zaloguj sie"
 AuthUserFile /etc/apache2/passwd
 Require valid-user

 RewriteEngine on
 RewriteRule ^$ /%{REMOTE_USER} [R,L]
 RewriteCond %{REMOTE_USER} ^(.+)
 RewriteCond %1:$1 !^([^:]+):\1$
 RewriteRule ^([^/]+)/ - [F,L]
</Directory>
</VirtualHost>

Duże i małe litery wpisywane w loginie przez userów

Zerknij tutaj.