Repository: privacyidea/privacyidea

Issue:
Allow empty database in sql resolver connection string
<!--
Thank you for filing a bug within privacyIDEA. The more information you provide, the easier it is to identify the problem!

Issues are not meant for asking questions! You're welcome to ask questions in our community forum: https://community.privacyidea.org

privacyIDEA is licensed under the AGPL and comes without warranty. If you require reliable, professional help, please consider getting either
- consultancy 
  (https://netknights.it/en/leistungen/one-time-services/)
- or the privacyIDEA Enterprise Edition 
  (https://netknights.it/en/leistungen/service-level-agreements/)
-->

### Top-level intent

What did you try to achieve?

I'm trying to configure a SQL resolver with a connection string using a DSN instead of server:port/db.
For example in the main config file this is possible by entering the connection string:

```
SQLALCHEMY_DATABASE_URI = 'oracle+cx_oracle://<user>:<password>@<dsn>'
```

In this case the dsn is then resolved by Oracle Instant client.

This use case might apply to people using ODBC as well.

### Steps to reproduce

1. Go to Config -> Users in the Web UI
2. Add a new sqlresolver
3. Configure Driver, Server, User and Password. Leave Port and Database empty.
4. Test the connection

### Expected outcome

What do you think, how the system should have worked?

The connection should have been successful.

### Actual outcome

What did actually happen?

It failed. In the logs you can see that the connection string contained the empty database converted to string as "None".

You can see this also happens if you leave the user field blank. See the log extract below.

### Configuration

* **privacyIDEA version**: 3.3.1
* **Installation method**: PIP
* **Python version**: 3.6.8
* **Operating system**: RHEL 7.8
* **Webserver**: NGINX Unit 1.17
* **Token database**: Oracle 19

### Log file

**Set PI_LOGLEVEL = logging.DEBUG in pi.cfg and take a look at the privacyidea.log!**
**If appropriate, attach the log file or paste relevant portions.**

```
[2020-06-29 11:37:12,310][77004][139727168707200][INFO][privacyidea.lib.resolvers.SQLIdResolver:657] using the connect string oracle+cx_oracle://<user>:<password>@<server>/None
[2020-06-29 11:47:24,754][77004][139727168707200][INFO][privacyidea.lib.resolvers.SQLIdResolver:657] using the connect string oracle+cx_oracle://None:<password>@<server>/None
```




Discussion:
Thank you for filing an issue and sharing your observations or ideas. Please be sure to provide as many information as possible to help us working on this issue.
This is likely because the default value of "" for this fields is not being applied correctly in https://github.com/privacyidea/privacyidea/blob/master/privacyidea/lib/resolvers/SQLIdResolver.py#L622

```python
        connect_string = u"{0!s}://{1!s}{2!s}{3!s}{4!s}{5!s}/{6!s}{7!s}".format(param.get("Driver", ""),
                                                   param.get("User", ""),
                                                   password,
                                                   "@" if (param.get("User")
                                                           or
                                                           password) else "",
                                                   param.get("Server", ""),
                                                   port,
                                                   param.get("Database", ""),
                                                   conParams)
```

The default value of "" in dict.get() is applied when the key is not found but in this case is found and mapped to None from the null in the database.

Hinted by this comment in StackOverflow: https://stackoverflow.com/questions/6130768/return-none-if-dictionary-key-is-not-available#comment50464375_6130800

Resolving patch:
diff --git a/privacyidea/lib/resolvers/SQLIdResolver.py b/privacyidea/lib/resolvers/SQLIdResolver.py
--- a/privacyidea/lib/resolvers/SQLIdResolver.py
+++ b/privacyidea/lib/resolvers/SQLIdResolver.py
@@ -619,15 +619,15 @@ def _create_connect_string(param):
             password = u":{0!s}".format(param.get("Password"))
         if param.get("conParams"):
             conParams = u"?{0!s}".format(param.get("conParams"))
-        connect_string = u"{0!s}://{1!s}{2!s}{3!s}{4!s}{5!s}/{6!s}{7!s}".format(param.get("Driver", ""),
-                                                   param.get("User", ""),
+        connect_string = u"{0!s}://{1!s}{2!s}{3!s}{4!s}{5!s}/{6!s}{7!s}".format(param.get("Driver") or "",
+                                                   param.get("User") or "",
                                                    password,
                                                    "@" if (param.get("User")
                                                            or
                                                            password) else "",
-                                                   param.get("Server", ""),
+                                                   param.get("Server") or "",
                                                    port,
-                                                   param.get("Database", ""),
+                                                   param.get("Database") or "",
                                                    conParams)
         # SQLAlchemy does not like a unicode connect string!
 #        if param.get("Driver").lower() == "sqlite":


Test patch: