Skip to content
This repository has been archived by the owner on Jul 12, 2024. It is now read-only.

Commit

Permalink
Changes for v12 and Nginx (#2)
Browse files Browse the repository at this point in the history
* first running version behind eliona's nginx reverse proxy

* add, mod tests

* undo changing port 80 to 8081, because it's used for public endpoints in app concept.
  • Loading branch information
christian-stauffer authored Feb 7, 2024
1 parent 7ee56e6 commit 506967c
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 33 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ This initialization can be handled by the `reset.sql` script.

- `LOG_LEVEL`(optional): defines the minimum level that should be [logged](https://github.com/eliona-smart-building-assistant/go-utils/blob/main/log/README.md). The default level is `info`.

- `SSO_SERVER_PORT` (optional): defines the port for Single Sign On Services, here SAML 2.0. The default value is Port `8080`.
- `SSO_SERVER_PORT` (optional): defines the port for Single Sign On Services, here SAML 2.0. The default value is Port `8081`.

### Database tables ###

Expand Down
75 changes: 75 additions & 0 deletions apiservices/api_configuration_service_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// This file is part of the eliona project.
// Copyright © 2023 Eliona by IoTEC AG. All Rights Reserved.
// ______ _ _
// | ____| (_)
// | |__ | |_ ___ _ __ __ _
// | __| | | |/ _ \| '_ \ / _` |
// | |____| | | (_) | | | | (_| |
// |______|_|_|\___/|_| |_|\__,_|
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NON INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

package apiservices_test

import (
"context"
"database/sql"
"fmt"
"saml-sso/apiservices"
"saml-sso/conf"
"testing"

"github.com/eliona-smart-building-assistant/go-eliona/app"
"github.com/eliona-smart-building-assistant/go-utils/db"
)

func TestApp_AppApi_Configuration_InitDB(t *testing.T) {
err := conf.UserLeicomInit()
if err != nil {
t.Log("user leicom, ", err)
}
err = conf.DropOwnSchema()
if err != nil {
// no error, if schema not exist
t.Log("drop schema, ", err)
}

execFunc := app.ExecSqlFile("../conf/init.sql")
err = execFunc(db.NewConnection())
if err != nil {
t.Error("init.sql failed, ", err)
}
}

func TestApp_AppApi_Configuration_GetConfig(t *testing.T) {
apiService := apiservices.NewConfigurationApiService()
cnf, err := apiService.GetConfiguration(context.Background())
if err != sql.ErrNoRows {
t.Error(err)
}
fmt.Println(cnf)
}

func TestApp_AppApi_Configuration_GetAttributeMapping(t *testing.T) {

}

func TestApp_AppApi_Configuration_GetPermissionMap(t *testing.T) {

}

func TestApp_AppApi_Configuration_PutConfig(t *testing.T) {

}

func TestApp_AppApi_Configuration_PutAttributeMapping(t *testing.T) {

}

func TestApp_AppApi_Configuration_PutPermissionMap(t *testing.T) {

}
11 changes: 8 additions & 3 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ package main

import (
"context"
"fmt"
"io"
"net/http"
"saml-sso/apiserver"
Expand All @@ -34,7 +33,7 @@ import (
const (
LOG_REGIO = "app"
API_SERVER_PORT = 3000
SSO_SERVER_PORT = 8081 // Publicly accessible. See wiki.
SSO_SERVER_PORT = 8081 // Publicly accessible without auth. See wiki.

SAML_SPECIFIC_ENDPOINT_PATH = "/saml/"
)
Expand Down Expand Up @@ -78,7 +77,8 @@ func run() {
apiPort := common.Getenv("API_SERVER_PORT", strconv.Itoa(API_SERVER_PORT))
samlSpPort := common.Getenv("SSO_SERVER_PORT", strconv.Itoa(SSO_SERVER_PORT))

fmt.Println(config.OwnUrl + ":" + apiPort)
log.Debug(LOG_REGIO, "own url: %v, api port: %v", config.OwnUrl, apiPort)

sp, err := saml.NewServiceProviderAdvanced(
config.ServiceProviderCertificate,
config.ServiceProviderPrivateKey,
Expand Down Expand Up @@ -124,6 +124,11 @@ func run() {
sp.GetMiddleWare().RequireAccount(authHandleFunc))
http.Handle(SAML_SPECIFIC_ENDPOINT_PATH, sp.GetMiddleWare())

// for backwards compatibility, can be removed when the frontend is reworked to the new generic /sso/* endpoints
http.Handle("/adfs/active/", activeHandleFunc)
http.Handle("/adfs/auth/",
sp.GetMiddleWare().RequireAccount(authHandleFunc))

log.Info(LOG_REGIO, "started @ %v", samlSpPort)
err = http.ListenAndServe(":"+samlSpPort, nil)
if err != nil {
Expand Down
10 changes: 5 additions & 5 deletions conf/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ CREATE TABLE IF NOT EXISTS saml_sp.attribute_map ( -- SAML session attribute nam

CREATE TABLE IF NOT EXISTS saml_sp.permissions (
id INT PRIMARY KEY NOT NULL DEFAULT 1 REFERENCES saml_sp.config(id) ON UPDATE CASCADE,
default_system_role TEXT NOT NULL DEFAULT 'regular' , -- reference to is maybe a bad idea (due to the new ACL)
default_proj_role TEXT NOT NULL DEFAULT 'operator' ,
system_role_saml_attribute TEXT ,
system_role_map JSON ,
proj_role_saml_attribute TEXT ,
default_system_role TEXT NOT NULL DEFAULT 'System user' , -- reference to is maybe a bad idea (due to the new ACL)
default_proj_role TEXT NOT NULL DEFAULT 'Project user' ,
system_role_saml_attribute TEXT ,
system_role_map JSON ,
proj_role_saml_attribute TEXT ,
proj_role_map JSON
) ;
44 changes: 43 additions & 1 deletion eliona/others.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ const (
"JOIN public.eliona_secret " +
"USING (schema), public.claim_jwt(role, now() + validity,user_id,null) jwt " +
"WHERE lower(u.email) = lower($1) AND NOT u.archived)"

OTHERS_GET_JWT_QUERY_V12 = "(SELECT public.make_jwt(jwt,secret) " +
"FROM public.eliona_user u " +
"JOIN public.acl_role r ON (u.role_id = r.role_id) " +
"JOIN public.project_user USING (user_id) " +
"JOIN public.eliona_secret USING (schema), " +
"public.claim_jwt(role, now() + validity,user_id,proj_id,r.role_id::text,schema) jwt " +
"WHERE lower(u.email) = lower($1) AND NOT u.archived)"
)

func GetElionaJsonWebToken(email string) (*string, error) {
Expand Down Expand Up @@ -70,9 +78,13 @@ func GetElionaJsonWebToken(email string) (*string, error) {
if strings.Contains(version, "v10.") {
log.Debug(LOG_REGIO, "eliona v10")
jwtQuery = OTHERS_GET_JWT_QUERY_V10
} else if strings.Contains(version, "v11.") &&
!strings.Contains(version, "v11.1.5") {
log.Debug(LOG_REGIO, "eliona v11")
jwtQuery = OTHERS_GET_JWT_QUERY_V11
} else {
// assume, that the version is newer (with ACL)
jwtQuery = OTHERS_GET_JWT_QUERY_V11
jwtQuery = OTHERS_GET_JWT_QUERY_V12
}

row = db.QueryRow(jwtQuery, email)
Expand All @@ -96,6 +108,36 @@ func UpdateElionaUserArchivedPhone(email string, phone *string, archived bool) e
return err
}

func GetFirstProjectId() (projectId string, err error) {
row := getDb().QueryRow("SELECT proj_id FROM eliona_project ORDER BY proj_id LIMIT 1")

if err = row.Err(); err != nil {
return
}

err = row.Scan(&projectId)
return
}

func GetRoleIdByDisplayName(displayName string) (roleId int, err error) {
row := getDb().QueryRow("SELECT role_id FROM acl_role WHERE displayname = $1", displayName)

if err = row.Err(); err != nil {
return
}

err = row.Scan(&roleId)
return
}

func SetProjectUser(projectId string, userId *string, roleId int) (err error) {

_, err = getDb().Exec("INSERT INTO project_user (proj_id, user_id, role_id) "+
"VALUES ($1, $2, $3)", projectId, userId, roleId)

return
}

func getDb() *sql.DB {
return db.Database(app.AppName())
}
11 changes: 11 additions & 0 deletions eliona/others_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,24 @@
package eliona_test

import (
"os"
"saml-sso/eliona"
"testing"

"github.com/eliona-smart-building-assistant/go-utils/log"
)

func TestApp_Others(t *testing.T) {

// this test needs a real eliona db to due missing tables
// in the test db
_, realDb := os.LookupEnv("REAL_DB")
if !realDb {
log.Warn("TestApp_Others", "test disabled because missing env var REAL_DB")
t.Log("TestApp_Others: test disabled because missing env var REAL_DB")
return
}

token, err := eliona.GetElionaJsonWebToken("su#@eliona.io")
if err != nil {
t.Error(err)
Expand Down
56 changes: 38 additions & 18 deletions eliona/single_sign_on.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package eliona
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"saml-sso/apiserver"
"saml-sso/conf"
Expand Down Expand Up @@ -79,7 +79,9 @@ func (s *SingleSignOn) ActiveHandle(w http.ResponseWriter, r *http.Request) {
}
}

w.Header().Add("Content-Type", "application/json")
w.WriteHeader(responseCode)

_, err = w.Write(responseMsg)
if err != nil {
log.Error(LOG_REGIO, "write internal server error: %v", err)
Expand All @@ -94,8 +96,9 @@ func (s *SingleSignOn) Authentication(w http.ResponseWriter, r *http.Request) {

mapping *apiserver.AttributeMap

loginEmail, userIp string
firstname, lastname, phone string
loginEmail, userIp string
firstname, lastname string
phone *string

user *api.User
jwt *string
Expand Down Expand Up @@ -133,7 +136,8 @@ func (s *SingleSignOn) Authentication(w http.ResponseWriter, r *http.Request) {
lastname = samlsp.AttributeFromContext(r.Context(), *mapping.LastName)
}
if mapping.Phone != nil && *mapping.Phone != "" {
phone = samlsp.AttributeFromContext(r.Context(), *mapping.Phone)
phoneS := samlsp.AttributeFromContext(r.Context(), *mapping.Phone)
phone = &phoneS
}

log.Info(LOG_REGIO, "User with firstname: %v, lastname: %v, email/login: "+
Expand All @@ -156,18 +160,19 @@ func (s *SingleSignOn) Authentication(w http.ResponseWriter, r *http.Request) {
errorMessage = []byte(err.Error())
goto internalServerError
}
}

err = UpdateElionaUserArchivedPhone(user.Email, &phone, s.userToArchive)
if err != nil {
log.Error(LOG_REGIO, "cannot set phone and archive flag: %v", err)
errorMessage = []byte(err.Error())
goto internalServerError
}
err = UpdateElionaUserArchivedPhone(user.Email, phone, s.userToArchive)
if err != nil {
log.Error(LOG_REGIO, "cannot set phone and archive flag: %v", err)
errorMessage = []byte(err.Error())
goto internalServerError
}

err = s.setUserPermissions(user.Email)
if err != nil {
log.Error(LOG_REGIO, "cannot set user permissions")
err = s.setUserPermissions(user.Id.Get())
if err != nil {
log.Error(LOG_REGIO, "cannot set user permissions: %v", err)
goto notAuthenticated
}
}

// obtain a jwt to login via cookies
Expand Down Expand Up @@ -218,7 +223,7 @@ internalServerError:
}
}

func (s *SingleSignOn) setUserPermissions(email string) error {
func (s *SingleSignOn) setUserPermissions(userId *string) error {

var (
err error
Expand All @@ -231,9 +236,24 @@ func (s *SingleSignOn) setUserPermissions(email string) error {
}

log.Info(LOG_REGIO,
"ToDo: add user to a project and set permissions according the configurations. %v",
"ToDo: permission map not finished yet. %v",
permissions)

err = errors.New("not implemented")
return err
projectId, err := GetFirstProjectId()
if err != nil {
return fmt.Errorf("cannot look up project id: %v", err)
}

roleId, err := GetRoleIdByDisplayName(permissions.DefaultProjRole)
if err != nil {
return fmt.Errorf("cannot get role id for role %s: %v",
permissions.DefaultProjRole, err)
}

if roleId <= 0 {
return fmt.Errorf("cannot get role id for %s",
permissions.DefaultProjRole)
}

return SetProjectUser(projectId, userId, roleId)
}
10 changes: 5 additions & 5 deletions openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -426,14 +426,14 @@ components:
type: string
readOnly: false
nullable: false
default: "regular"
example: "regular"
default: 'System user'
example: 'System user'
default_proj_role:
type: string
nullable: false
readOnly: false
default: "operator"
example: "operator"
default: 'Project user'
example: 'Project user'
system_role_saml_attribute:
type: string
nullable: true
Expand Down Expand Up @@ -464,7 +464,7 @@ components:
elionaRole:
type: string
nullable: false
example: "admin"
example: 'System user'
samlValue:
type: string
nullable: false
Expand Down

0 comments on commit 506967c

Please sign in to comment.