Project

General

Profile

PageMaker » History » Version 13

Jan Klopper, 2017-02-01 13:49

1 3 Elmer de Looff
PageMaker is the _Controller_ of the MVC approach in µWeb. After a request is received by the web server (either [[Standalone]] or [[Apache]]) and wrapped inside a [[Request]] object, it is [[Request Router|routed]] here to be answered.
2 1 Elmer de Looff
3 3 Elmer de Looff
In the PageMaker, there might be database lookups done through the [[Model|data abstraction layer (model)]] and likely output is sent back making use of the [[TemplateParser]].
4 3 Elmer de Looff
5 3 Elmer de Looff
{{toc}}
6 3 Elmer de Looff
7 3 Elmer de Looff
h1. A very minimal PageMaker
8 3 Elmer de Looff
9 3 Elmer de Looff
In the simplest form, a PageMaker for a project subclasses from µWeb's default @PageMaker@ class and provides its own methods to handle requests. The full source for this would look something like this:
10 3 Elmer de Looff
11 3 Elmer de Looff
<pre><code class="python">
12 3 Elmer de Looff
#!/usr/bin/python
13 3 Elmer de Looff
"""PageMaker demonstration module"""
14 3 Elmer de Looff
15 3 Elmer de Looff
# uWeb framework
16 3 Elmer de Looff
import uweb
17 3 Elmer de Looff
18 5 Elmer de Looff
class Minimalist(uweb.PageMaker):
19 3 Elmer de Looff
  def Index(self):
20 3 Elmer de Looff
    return 'Welcome to our website, it is still very much under construction.'
21 3 Elmer de Looff
22 3 Elmer de Looff
  def Catchall(self, path):
23 3 Elmer de Looff
    return 'The requested page %r does not exist yet' % path
24 3 Elmer de Looff
</code></pre>
25 3 Elmer de Looff
26 1 Elmer de Looff
h1. DebuggingPageMaker
27 3 Elmer de Looff
28 3 Elmer de Looff
Before we do anything else, during development you are _strongly advised_ to use µWeb's @DebuggingPageMaker@. This has a lot of additional features for when something goes wrong on the server side. When the regular PageMaker runs into a server side error, it returns a very plain HTTP 500 response:
29 3 Elmer de Looff
30 3 Elmer de Looff
<pre>
31 3 Elmer de Looff
INTERNAL SERVER ERROR (HTTP 500) DURING PROCESSING OF '/'
32 3 Elmer de Looff
</pre>
33 3 Elmer de Looff
34 4 Elmer de Looff
Where @'/'@ is the path requested by the client. When running @DebuggingPageMaker@ there is a significantly more helpful (for the developer at least) page whenever an internal server error is encountered. It will show a full stack trace, the local variables on each stack level (typically at the point of calling another function), which helps to arrive to the point of failure more quickly.
35 4 Elmer de Looff
36 7 Jan Klopper
To use, just subclass your PageMaker from DebuggingPageMaker:
37 7 Jan Klopper
<pre><code class="python">
38 7 Jan Klopper
class Minimalist(uweb.DebuggingPageMaker)
39 7 Jan Klopper
</code></pre>
40 7 Jan Klopper
41 4 Elmer de Looff
Example Internal Server Error response "as image":http://bugs.underdark.nl/attachments/download/185/http500_full.png or in the "µWeb demo project":http://info.underdark.nl/haltandcatchfire?debug
42 3 Elmer de Looff
43 3 Elmer de Looff
In all cases, an internal server error will cause a full stacktrace to be logged in the log file database.
44 1 Elmer de Looff
45 2 Elmer de Looff
h1. Templateparser
46 1 Elmer de Looff
47 2 Elmer de Looff
The µWeb *[[TemplateParser]]* is available on the standard PageMaker instance. When using PageMaker, an instantiated TemplateParser instance is available through the @parser@ member of PageMaker. Basic usage looks like this:
48 2 Elmer de Looff
49 2 Elmer de Looff
<pre><code class="python">
50 2 Elmer de Looff
import uweb
51 2 Elmer de Looff
import time
52 2 Elmer de Looff
53 5 Elmer de Looff
class TemplateDemo(uweb.PageMaker):
54 2 Elmer de Looff
  def VersionPage(self):
55 2 Elmer de Looff
    return self.parser.Parse(
56 2 Elmer de Looff
      'version.utp', year=time.strftime('%Y'), version=uweb.__version__)
57 2 Elmer de Looff
</code></pre>
58 2 Elmer de Looff
59 2 Elmer de Looff
The example template for the above file could look something like this:
60 2 Elmer de Looff
61 2 Elmer de Looff
<pre><code class="html">
62 2 Elmer de Looff
<!DOCTYPE html>
63 2 Elmer de Looff
<html>
64 2 Elmer de Looff
  <head>
65 2 Elmer de Looff
    <title>µWeb version info</title>
66 2 Elmer de Looff
  </head>
67 2 Elmer de Looff
  <body>
68 2 Elmer de Looff
    <p>µWeb version [version] - Copyright 2010-[year] Underdark</p>
69 2 Elmer de Looff
  </body>
70 2 Elmer de Looff
</html>
71 2 Elmer de Looff
</code></pre>
72 2 Elmer de Looff
73 2 Elmer de Looff
And would result in the following output:
74 2 Elmer de Looff
75 2 Elmer de Looff
<pre><code class="html">
76 2 Elmer de Looff
<!DOCTYPE html>
77 2 Elmer de Looff
<html>
78 2 Elmer de Looff
  <head>
79 2 Elmer de Looff
    <title>µWeb version info</title>
80 2 Elmer de Looff
  </head>
81 2 Elmer de Looff
  <body>
82 2 Elmer de Looff
    <p>µWeb version 0.12 - Copyright 2010-2012 Underdark</p>
83 2 Elmer de Looff
  </body>
84 2 Elmer de Looff
</html>
85 2 Elmer de Looff
</code></pre>
86 2 Elmer de Looff
87 2 Elmer de Looff
Full documentation, with plenty of example template uses can be found on the [[TemplateParser|TemplateParser wiki-entry]].
88 2 Elmer de Looff
89 2 Elmer de Looff
h2. Template directory configuration
90 2 Elmer de Looff
91 2 Elmer de Looff
By default, template are loaded from the 'templates' directory that is expected to be on the same path as the pagemaker module. If your pagemaker is located on @/var/www/uweb_project/project.py@, then templates will be automatically loaded from @/var/www/uweb_project/templates/@.
92 2 Elmer de Looff
93 2 Elmer de Looff
To change the default template loading path, define a new path in the class variable @TEMPLATE_DIR@. This should be a relative path (and defaults to @'templates'@).
94 2 Elmer de Looff
95 6 Elmer de Looff
h1. Serving static content
96 1 Elmer de Looff
97 6 Elmer de Looff
Your website most likely has a few static files that need to be served up. If you have a large website you would run many of these from a separate domain (to reduce the amount of overhead from a heavy web server and complex processes), but often there are at least some files that need to be served up from the local disk.
98 1 Elmer de Looff
99 6 Elmer de Looff
µWeb has built-in facilities to serve static files, which prevent filesystem traversal by those lesser-behaved browsers. A browser requesting http://example.com/../secret_configuration.txt should *not* get the keys to your database server. The static handler has a base (configurable) directory from which all static content is served. For a client it is impossible to 'browse' up from that directory, preventing these leaks of information.
100 6 Elmer de Looff
101 6 Elmer de Looff
The MIME-Type for the content will be determined using the @mimetypes@ module (available by default in Python), based on the file's extension.
102 6 Elmer de Looff
103 6 Elmer de Looff
h2. Static handler demonstration
104 6 Elmer de Looff
105 6 Elmer de Looff
In your router configuration, add a route the directs to the static handler:
106 1 Elmer de Looff
107 7 Jan Klopper
<pre><code class="python">
108 6 Elmer de Looff
ROUTES = (
109 6 Elmer de Looff
    ...
110 6 Elmer de Looff
    ('/images/(.*)', 'Static'),
111 6 Elmer de Looff
    ...
112 6 Elmer de Looff
    )
113 6 Elmer de Looff
</code></pre>
114 6 Elmer de Looff
115 6 Elmer de Looff
This will cause the following behaviour:
116 6 Elmer de Looff
* A browser requesting http://example.com/images/fish.jpg will be presented with the content from @'static/fish.jpg'@
117 6 Elmer de Looff
* A browser requesting http://example.com/images/../../secret.txt will be presented with the content from @'static/secret.txt'@ (if it exists)
118 6 Elmer de Looff
119 1 Elmer de Looff
h2. Static directory configuration
120 1 Elmer de Looff
121 6 Elmer de Looff
As can be seen in the previous example, the content is served from the @'static'@ directory (this is not dependent on the selected route. This path is relative to the absolute path of the PageMaker itself. If the PageMaker module exists on @'/var/www/project/controller.py'@ then the default static directory is @'/var/www/project/static/'@.
122 1 Elmer de Looff
123 6 Elmer de Looff
This default path can be changed by setting the @PUBLIC_DIR@ class variable of PageMaker. The path can be made absolute simply by providing one:
124 6 Elmer de Looff
125 6 Elmer de Looff
<pre><code class="python">
126 6 Elmer de Looff
import uweb
127 6 Elmer de Looff
128 6 Elmer de Looff
class DifferentPublic(uweb.PageMaker):
129 6 Elmer de Looff
  PUBLIC_DIR = '/var/www/project/public_http'
130 6 Elmer de Looff
</code></pre>
131 6 Elmer de Looff
132 6 Elmer de Looff
h2. 404 on static content
133 6 Elmer de Looff
134 6 Elmer de Looff
Whenever a request for static content (through @Static@) cannot be fulfilled, the method @_StaticNotFound@ is called, with the requested relative path as the sole argument. The default response for which is a simple plain text:
135 6 Elmer de Looff
136 6 Elmer de Looff
<pre><code class="python">
137 6 Elmer de Looff
  def _StaticNotFound(self, _path):
138 6 Elmer de Looff
    message = 'This is not the path you\'re looking for. No such file %r' % (
139 6 Elmer de Looff
      self.req.env['PATH_INFO'])
140 6 Elmer de Looff
    return response.Response(message, content_type='text/plain', httpcode=404)
141 6 Elmer de Looff
</code></pre>
142 6 Elmer de Looff
143 6 Elmer de Looff
Override this page if you want to provide your user with a more informative or styled response.
144 6 Elmer de Looff
145 8 Jan Klopper
h1. Model admin
146 8 Jan Klopper
147 8 Jan Klopper
µWeb comes with a minimal admin interface based on the [[model]]
148 8 Jan Klopper
149 8 Jan Klopper
h2. Add the Admin mixin class to PageMaker
150 8 Jan Klopper
151 8 Jan Klopper
This is as simple as adding the Admin mixin class as one of the ancestors of your PageMaker. It is generally advisable to place mixin classes before the base class):
152 8 Jan Klopper
<pre><code class="python">
153 9 Elmer de Looff
# Package imports
154 8 Jan Klopper
from . import model
155 8 Jan Klopper
156 9 Elmer de Looff
# uWeb imports
157 8 Jan Klopper
import uweb
158 8 Jan Klopper
from uweb.pagemaker import admin
159 8 Jan Klopper
160 9 Elmer de Looff
class Pages(admin.AdminMixin, uweb.PageMaker):
161 8 Jan Klopper
</code></pre>
162 8 Jan Klopper
163 8 Jan Klopper
After this, you will want to tell the admin code where to find your model, this happens inside your PageMaker class:
164 8 Jan Klopper
<pre><code class="python">
165 8 Jan Klopper
  ADMIN_MODEL = model
166 8 Jan Klopper
</code></pre>
167 8 Jan Klopper
168 8 Jan Klopper
h2. Router details
169 8 Jan Klopper
170 8 Jan Klopper
To setup a route to the admin pages, you will need to add a specific route to your router.
171 8 Jan Klopper
<pre><code class="python">
172 8 Jan Klopper
ROUTES = (
173 8 Jan Klopper
    ...
174 8 Jan Klopper
    # Admin routes
175 8 Jan Klopper
    ('/admin(/.*)?', '_Admin'),
176 8 Jan Klopper
    ...
177 8 Jan Klopper
)
178 8 Jan Klopper
</code></pre>
179 8 Jan Klopper
180 8 Jan Klopper
After this, you can navigate to http://localhost:8082/admin to see the database's contents, manipulate records and view the documentation stored inside your model's methods.
181 8 Jan Klopper
182 6 Elmer de Looff
h1. Login and sessions
183 6 Elmer de Looff
184 6 Elmer de Looff
h2. OpenID
185 6 Elmer de Looff
186 6 Elmer de Looff
To enable users of your website to log in using OpenID, there are only a few steps that need to be taken:
187 6 Elmer de Looff
188 6 Elmer de Looff
h3. Add the OpenID mixin class to PageMaker
189 6 Elmer de Looff
190 6 Elmer de Looff
This is as simple as adding the OpenID mixin class as one of the ancestors of your PageMaker. It is generally advisable to place mixin classes before the base class):
191 6 Elmer de Looff
192 6 Elmer de Looff
<pre><code class="python">
193 6 Elmer de Looff
import uweb
194 6 Elmer de Looff
from uweb.pagemaker import login
195 6 Elmer de Looff
196 6 Elmer de Looff
class Pages(login.OpenIdMixin, uweb.PageMaker):
197 6 Elmer de Looff
</code></pre>
198 6 Elmer de Looff
199 6 Elmer de Looff
h3. Set up routes to the OpenID validator
200 6 Elmer de Looff
201 6 Elmer de Looff
The following routes (or similar ones) should be added to the [[router]]:
202 6 Elmer de Looff
203 6 Elmer de Looff
<pre><code class="python">
204 6 Elmer de Looff
ROUTES = (
205 6 Elmer de Looff
    ...
206 6 Elmer de Looff
    ('/OpenIDLogin/?(\w+)?', '_OpenIdInitiate')
207 6 Elmer de Looff
    ('/OpenIDValidate', '_OpenIdValidate')
208 6 Elmer de Looff
    ...
209 6 Elmer de Looff
    )
210 6 Elmer de Looff
</code></pre>
211 6 Elmer de Looff
212 6 Elmer de Looff
The optional capture after @'OpenIDLogin'@ here is to provide the optional provider URL (instead of through the POST field @'openid_provider'@).
213 6 Elmer de Looff
214 10 Arjen Pander
h3. Add handlers to PageMaker for success, failure, etc:
215 6 Elmer de Looff
216 6 Elmer de Looff
<pre><code class="python">
217 6 Elmer de Looff
  def OpenIdAuthCancel(self, message):
218 6 Elmer de Looff
    return 'OpenID Authentication canceled by user: %s' % message
219 6 Elmer de Looff
220 6 Elmer de Looff
  def OpenIdAuthFailure(self, message):
221 6 Elmer de Looff
    return 'Authentication failed: %s' % message
222 6 Elmer de Looff
223 6 Elmer de Looff
  def OpenIdAuthSuccess(self, auth_dict):
224 6 Elmer de Looff
    # Authentication succeeded, the auth_dict contains the information we received from the provider
225 6 Elmer de Looff
    #
226 6 Elmer de Looff
    # Next: Retrieve user information from database, or create a new user
227 6 Elmer de Looff
    #       Store the user's session (in the database, cookie, or both)
228 6 Elmer de Looff
    session_id = base64.urlsafe_b64encode(os.urandom(30))
229 6 Elmer de Looff
    self.req.AddCookie('OpenIDSession', session_id, max_age=86400)
230 6 Elmer de Looff
    return 'OpenID Authentication successful!'
231 6 Elmer de Looff
232 6 Elmer de Looff
  def OpenIdProviderBadLink(self, message):
233 6 Elmer de Looff
    return 'Bad OpenID Provider URL: %s' % message
234 6 Elmer de Looff
235 6 Elmer de Looff
  def OpenIdProviderError(self, message):
236 6 Elmer de Looff
    return 'The OpenID provider did not respond as expected: %r' % message
237 6 Elmer de Looff
</code></pre>
238 6 Elmer de Looff
239 6 Elmer de Looff
h2. Underdark Login Framework
240 6 Elmer de Looff
241 6 Elmer de Looff
Using the Underdark Login Framework requires steps comparable to using OpenID, but comes with a little more default setup. The system has two modes for logging in, one that is a straightforward plaintext form submit. This is slightly easier to implement, but when used without SSL it is highly vulnerable to man-in-the-middle (MITM) attacks. The second mode is a Javascript enabled mode where the password is hashed (using the SHA-1 algorithm), and to prevent replay attacks (from a MITM), a random 'challenge' is provided for each login attempt, which is also hashed with the result. This prevents a MITM from learning the password value, or using it to log in later (though the plaintext communication remains visible).
242 6 Elmer de Looff
243 6 Elmer de Looff
h3. Add the ULF mixin class to PageMaker
244 6 Elmer de Looff
245 6 Elmer de Looff
<pre><code class="python">
246 6 Elmer de Looff
import uweb
247 6 Elmer de Looff
from uweb.pagemaker import login
248 6 Elmer de Looff
249 6 Elmer de Looff
class Pages(login.LoginMixin, uweb.PageMaker):
250 6 Elmer de Looff
</code></pre>
251 6 Elmer de Looff
252 6 Elmer de Looff
h3. Set up routes to the OpenID validator
253 6 Elmer de Looff
254 6 Elmer de Looff
The following routes (or similar ones) should be added to the [[router]]:
255 6 Elmer de Looff
256 6 Elmer de Looff
<pre><code class="python">
257 6 Elmer de Looff
ROUTES = (
258 6 Elmer de Looff
    ...
259 6 Elmer de Looff
    ('/ULF-Challenge', '_ULF_Challenge'),
260 6 Elmer de Looff
    ('/ULF-Login', '_ULF_Verify'),
261 6 Elmer de Looff
    ...
262 6 Elmer de Looff
    )
263 6 Elmer de Looff
</code></pre>
264 6 Elmer de Looff
265 10 Arjen Pander
h3. Add handlers to PageMaker for success and failure:
266 6 Elmer de Looff
267 6 Elmer de Looff
<pre><code class="python">
268 6 Elmer de Looff
  def _ULF_Failure(self, secure):
269 6 Elmer de Looff
    reutrn 'ULF authentication failed (secure mode: %d)' % secure
270 6 Elmer de Looff
271 6 Elmer de Looff
  def _ULF_Success(self, secure):
272 6 Elmer de Looff
    return 'ULF authentication successful! (secure mode: %d)' % secure
273 6 Elmer de Looff
</code></pre>
274 2 Elmer de Looff
275 2 Elmer de Looff
h1. Persistent storage between requests
276 1 Elmer de Looff
277 1 Elmer de Looff
µWeb allows you to store objects in a process-persistent storage. This means that the storage will be properly persistent and available when µWeb is in [[standalone]] mode. When running on top of [[apache]], this persistence is only as good as the apache process, which is typically a couple hundred to a few thousand requests.
278 2 Elmer de Looff
279 1 Elmer de Looff
h2. Default users of the persistent storage
280 1 Elmer de Looff
281 1 Elmer de Looff
By default, the [[TemplateParser]] and the various database connectors are stored in the persistent storage. This has the benefit that pre-parsed templates will not need to be read from disk on subsequent requests. For databases the benefit is that connections need not be made on-the-fly, but can mostly be retrieved from the storage. 
282 2 Elmer de Looff
283 1 Elmer de Looff
h2. Storing persistent values
284 1 Elmer de Looff
285 1 Elmer de Looff
Storing persistent values is done with the @Set@ method, as follows:
286 1 Elmer de Looff
287 1 Elmer de Looff
<pre><code class="python">
288 1 Elmer de Looff
def _PostInit(self):
289 1 Elmer de Looff
  if 'connection' not in self.persistent:
290 1 Elmer de Looff
    self.persistent.Set('connection', self._MakeConnection())
291 1 Elmer de Looff
</code></pre>
292 1 Elmer de Looff
293 1 Elmer de Looff
In the example above, the database connection is only created, and added to the persistent storage, if it's not already present. This way expensive but reusable actions can be optimized by performing them only once (or once every few so many requests, if running on Apache).
294 2 Elmer de Looff
295 1 Elmer de Looff
h2. Retrieving persistent values
296 1 Elmer de Looff
297 1 Elmer de Looff
Retrieving stored values works much like this, but uses the @Get@ method:
298 1 Elmer de Looff
299 1 Elmer de Looff
<pre><code class="python">
300 1 Elmer de Looff
def DatabaseAccess(self):
301 1 Elmer de Looff
  with self.persistent.Get('connection') as cursor:
302 1 Elmer de Looff
    cursor.Execute('INSERT INTO `message` SET `text` = "success!"')
303 1 Elmer de Looff
</code></pre>
304 1 Elmer de Looff
305 1 Elmer de Looff
This uses the connection we created (or still had) during @_PostInit@, and uses it to update the database.
306 1 Elmer de Looff
307 11 Arjen Pander
In case a key has is not present in the persistent storage (because it wasn't set in the process' lifetime or because it was explicitly dropped), the @Get@ method has an optional second argument, that is returned when the key is not present:
308 1 Elmer de Looff
309 1 Elmer de Looff
<pre><code class="python">
310 1 Elmer de Looff
def FirstVisit(self):
311 1 Elmer de Looff
  when = self.persistent.Get('first_visit_time', 'just now')
312 1 Elmer de Looff
  return 'Your first visit was %s.' % when
313 1 Elmer de Looff
</code></pre>
314 1 Elmer de Looff
315 1 Elmer de Looff
This will return the stored date and time when there was a previously recorded visit, or the text _just now_ if there was no previous time logged.
316 1 Elmer de Looff
317 1 Elmer de Looff
Finally, the persistent storage has a @SetDefault@ method, that acts much like the similarly named dictionary method. It returns the value for the given key, but if it's not present, it will set the key to the provided value, and return it as well. With this, we can improve on our first-visit tracker, and in one call retrieve or store the first time someone visited:
318 1 Elmer de Looff
319 1 Elmer de Looff
<pre><code class="python">
320 1 Elmer de Looff
def FirstVisit(self):
321 1 Elmer de Looff
  when = self.persistent.SetDefault('first_visit_time', datetime.datetime.now())
322 1 Elmer de Looff
  return 'Your first visit was %s.' % when
323 1 Elmer de Looff
</code></pre>
324 2 Elmer de Looff
325 1 Elmer de Looff
h2. Deleting persistent values
326 1 Elmer de Looff
327 1 Elmer de Looff
If for any reason you need to delete a value from the persistent storage, this can be done using the @Del@ method. The given key name is removed from the storage. *N.B.:* If the key was already removed from the storage (this can happen if the delete code runs more than once, or the key was not defined in the process' lifetime), no error is raised. It is assumed that removing the key is the only desired action.
328 1 Elmer de Looff
329 1 Elmer de Looff
<pre><code class="python">
330 1 Elmer de Looff
def DeletePersistentKey(self, key):
331 1 Elmer de Looff
  self.persistent.Del(key)
332 1 Elmer de Looff
</code></pre>
333 12 Jan Klopper
334 12 Jan Klopper
h2. Logging:
335 12 Jan Klopper
336 12 Jan Klopper
You can use the bundled logging tool and vieuwer to log various messages and view them.
337 12 Jan Klopper
For example:
338 12 Jan Klopper
339 12 Jan Klopper
In a pagemaker function you can do:
340 12 Jan Klopper
<pre><code class="python">
341 12 Jan Klopper
uweb.logging.LogDebug('Some Debug message')
342 13 Jan Klopper
uweb.logging.LogDebug('Some Debug message', variables, a=b, c=d)
343 12 Jan Klopper
</code></pre>
344 12 Jan Klopper
345 12 Jan Klopper
Both arguments and keyword based arguments will be logged.
346 12 Jan Klopper
347 12 Jan Klopper
Various log levels are present, each with their own method Name
348 12 Jan Klopper
349 12 Jan Klopper
* LogDebug
350 12 Jan Klopper
* LoginInfo
351 12 Jan Klopper
* LogWarning
352 12 Jan Klopper
* LogError
353 12 Jan Klopper
* LogException
354 12 Jan Klopper
* LogCritical
355 12 Jan Klopper
356 12 Jan Klopper
The logged errors, including those that the platform itself logs when using for example the 'debuggingpagemaker' are stored in you log folder inside a sqlite database.
357 12 Jan Klopper
A viewer for theses databases is included in he package and can be started by issuing:
358 12 Jan Klopper
<pre><code class="bash">
359 13 Jan Klopper
uweb start logviewer
360 1 Elmer de Looff
</code></pre>
361 13 Jan Klopper
362 13 Jan Klopper
which will start a webserver on port 8001.
363 12 Jan Klopper
364 12 Jan Klopper
h3. Log locations:
365 12 Jan Klopper
By default logs are stored in:
366 12 Jan Klopper
* /var/log/underdark
367 12 Jan Klopper
* ~/.underdark
368 12 Jan Klopper
The choice of this depends on the write rights the user running the application has.