diff --git a/backend/WebUI/api_webui.go b/backend/WebUI/api_webui.go index ee8c8581d6d3bcba19cf641aaa9b3f38b3a24992..83a8847600df5e72ad68eb1d94fa6c4ecefe689b 100644 --- a/backend/WebUI/api_webui.go +++ b/backend/WebUI/api_webui.go @@ -5,10 +5,16 @@ import ( "encoding/json" "fmt" "net/http" + "os" + "reflect" + "time" "strings" + "github.com/dgrijalva/jwt-go" "github.com/gin-gonic/gin" + "github.com/google/uuid" "go.mongodb.org/mongo-driver/bson" + "golang.org/x/crypto/bcrypt" "github.com/free5gc/MongoDBLibrary" "github.com/free5gc/openapi/models" @@ -24,6 +30,8 @@ const ( amPolicyDataColl = "policyData.ues.amData" smPolicyDataColl = "policyData.ues.smData" flowRuleDataColl = "policyData.ues.flowRule" + userDataColl = "userData" + tenantDataColl = "tenantData" ) var httpsClient *http.Client @@ -79,6 +87,47 @@ func sendResponseToClient(c *gin.Context, response *http.Response) { c.JSON(response.StatusCode, jsonData) } +func sendResponseToClientFilterTenant(c *gin.Context, response *http.Response, tenantId string) { + // Subscription data. + filterTenantIdOnly := bson.M{"tenantId": tenantId} + amDataList := MongoDBLibrary.RestfulAPIGetMany(amDataColl, filterTenantIdOnly) + + tenantCheck := func(supi string) bool { + for _, amData := range amDataList { + if supi == amData["ueId"] && tenantId == amData["tenantId"] { + return true + } + } + return false + } + + // Response data. + var jsonData interface{} + json.NewDecoder(response.Body).Decode(&jsonData) + + s := reflect.ValueOf(jsonData) + if s.Kind() != reflect.Slice { + c.JSON(response.StatusCode, jsonData) + return + } + + var sliceData []interface{} + for i := 0; i < s.Len(); i++ { + mapData := s.Index(i).Interface() + m := reflect.ValueOf(mapData) + for _, key := range m.MapKeys() { + if key.String() == "Supi" { + strct := m.MapIndex(key) + if tenantCheck(strct.Interface().(string)) { + sliceData = append(sliceData, mapData) + } + } + } + } + + c.JSON(response.StatusCode, sliceData) +} + func GetSampleJSON(c *gin.Context) { setCorsHeader(c) @@ -270,24 +319,449 @@ func GetSampleJSON(c *gin.Context) { c.JSON(http.StatusOK, subsData) } +type OAuth struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + TokenType string `json:"token_type"` + ExpiresIn int `json:"expires_in"` +} + +type LoginRequest struct { + Username string `json:"username"` + Password string `json:"password"` +} + +func JWT(email, userId, tenantId string) string { + token := jwt.New(jwt.SigningMethodHS256) + + claims := token.Claims.(jwt.MapClaims) + claims["sub"] = userId + claims["iat"] = time.Now() + claims["exp"] = time.Now().Add(time.Hour * 24).Unix() + claims["email"] = email + claims["tenantId"] = tenantId + + tokenString, _ := token.SignedString([]byte(os.Getenv("SIGNINGKEY"))) + + return tokenString +} + +func generateHash(password string) { + hash, _ := bcrypt.GenerateFromPassword([]byte(password), 12) + logger.WebUILog.Warnln("Password hash:", hash) +} + +func Login(c *gin.Context) { + setCorsHeader(c) + + login := LoginRequest{} + err := json.NewDecoder(c.Request.Body).Decode(&login) + if err != nil { + logger.WebUILog.Warnln("JSON decode error", err) + c.JSON(http.StatusInternalServerError, gin.H{}) + return + } + + generateHash(login.Password) + + filterEmail := bson.M{"email": login.Username} + userData := MongoDBLibrary.RestfulAPIGetOne(userDataColl, filterEmail) + + if len(userData) == 0 { + logger.WebUILog.Warnln("Can't find user email", login.Username) + c.JSON(http.StatusForbidden, gin.H{}) + return + } + + hash := userData["encryptedPassword"].(string) + + err = bcrypt.CompareHashAndPassword([]byte(hash), []byte(login.Password)) + if err != nil { + logger.WebUILog.Warnln("Password incorrect", login.Username) + c.JSON(http.StatusForbidden, gin.H{}) + return + } + + userId := userData["userId"].(string) + tenantId := userData["tenantId"].(string) + + logger.WebUILog.Warnln("Login success", login.Username) + logger.WebUILog.Warnln("userid", userId) + logger.WebUILog.Warnln("tenantid", tenantId) + + token := JWT(login.Username, userId, tenantId) + logger.WebUILog.Warnln("token", token) + + oauth := OAuth{} + oauth.AccessToken = token + c.JSON(http.StatusOK, oauth) +} + +// Placeholder to handle logout. +func Logout(c *gin.Context) { + setCorsHeader(c) + // Needs to invalidate access_token. + c.JSON(http.StatusOK, gin.H{}) +} + +type AuthSub struct { + models.AuthenticationSubscription + TenantId string `json:"tenantId" bson:"tenantId"` +} + +// Parse JWT +func ParseJWT(tokenStr string) jwt.MapClaims { + token, _ := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) { + return []byte(os.Getenv("SIGNINGKEY")), nil + }) + + claims, _ := token.Claims.(jwt.MapClaims) + + return claims +} + +// Check of admin user. This should be done with proper JWT token. +func CheckAuth(c *gin.Context) bool { + tokenStr := c.GetHeader("Token") + if tokenStr == "admin" { + return true + } else { + return false + } +} + +// Tenat ID +func GetTenantId(c *gin.Context) string { + tokenStr := c.GetHeader("Token") + if tokenStr == "admin" { + return "" + } + claims := ParseJWT(tokenStr) + return claims["tenantId"].(string) +} + +// Tenant +func GetTenants(c *gin.Context) { + setCorsHeader(c) + + if !CheckAuth(c) { + c.JSON(http.StatusNotFound, bson.M{}) + return + } + + tenantDataInterface := MongoDBLibrary.RestfulAPIGetMany(tenantDataColl, bson.M{}) + var tenantData []Tenant + json.Unmarshal(sliceToByte(tenantDataInterface), &tenantData) + + c.JSON(http.StatusOK, tenantData) +} + +func GetTenantByID(c *gin.Context) { + setCorsHeader(c) + + if !CheckAuth(c) { + c.JSON(http.StatusNotFound, bson.M{}) + return + } + + tenantId := c.Param("tenantId") + + filterTenantIdOnly := bson.M{"tenantId": tenantId} + tenantDataInterface := MongoDBLibrary.RestfulAPIGetOne(tenantDataColl, filterTenantIdOnly) + if len(tenantDataInterface) == 0 { + c.JSON(http.StatusNotFound, bson.M{}) + return + } + + var tenantData Tenant + json.Unmarshal(mapToByte(tenantDataInterface), &tenantData) + + c.JSON(http.StatusOK, tenantData) +} + +func PostTenant(c *gin.Context) { + setCorsHeader(c) + + if !CheckAuth(c) { + c.JSON(http.StatusNotFound, bson.M{}) + return + } + + var tenantData Tenant + if err := c.ShouldBindJSON(&tenantData); err != nil { + c.JSON(http.StatusBadRequest, gin.H{}) + return + } + + if tenantData.TenantId == "" { + tenantData.TenantId = uuid.Must(uuid.NewRandom()).String() + } + + tenantBsonM := toBsonM(tenantData) + filterTenantIdOnly := bson.M{"tenantId": tenantData.TenantId} + MongoDBLibrary.RestfulAPIPost(tenantDataColl, filterTenantIdOnly, tenantBsonM) + + c.JSON(http.StatusOK, tenantData) +} + +func PutTenantByID(c *gin.Context) { + setCorsHeader(c) + + if !CheckAuth(c) { + c.JSON(http.StatusNotFound, bson.M{}) + return + } + + tenantId := c.Param("tenantId") + + filterTenantIdOnly := bson.M{"tenantId": tenantId} + tenantDataInterface := MongoDBLibrary.RestfulAPIGetOne(tenantDataColl, filterTenantIdOnly) + if len(tenantDataInterface) == 0 { + c.JSON(http.StatusNotFound, bson.M{}) + return + } + + var tenantData Tenant + if err := c.ShouldBindJSON(&tenantData); err != nil { + c.JSON(http.StatusBadRequest, gin.H{}) + return + } + tenantData.TenantId = tenantId + + tenantBsonM := toBsonM(tenantData) + filterTenantIdOnly = bson.M{"tenantId": tenantId} + MongoDBLibrary.RestfulAPIPost(tenantDataColl, filterTenantIdOnly, tenantBsonM) + + c.JSON(http.StatusOK, gin.H{}) +} + +func DeleteTenantByID(c *gin.Context) { + setCorsHeader(c) + + if !CheckAuth(c) { + c.JSON(http.StatusNotFound, bson.M{}) + return + } + + tenantId := c.Param("tenantId") + filterTenantIdOnly := bson.M{"tenantId": tenantId} + + MongoDBLibrary.RestfulAPIDeleteMany(amDataColl, filterTenantIdOnly) + MongoDBLibrary.RestfulAPIDeleteMany(userDataColl, filterTenantIdOnly) + MongoDBLibrary.RestfulAPIDeleteOne(tenantDataColl, filterTenantIdOnly) + + c.JSON(http.StatusOK, gin.H{}) +} + +// Utility function. +func GetTenantById(tenantId string) map[string]interface{} { + filterTenantIdOnly := bson.M{"tenantId": tenantId} + return MongoDBLibrary.RestfulAPIGetOne(tenantDataColl, filterTenantIdOnly) +} + +// Users +func GetUsers(c *gin.Context) { + setCorsHeader(c) + + if !CheckAuth(c) { + c.JSON(http.StatusNotFound, bson.M{}) + return + } + + tenantId := c.Param("tenantId") + if len(GetTenantById(tenantId)) == 0 { + c.JSON(http.StatusNotFound, bson.M{}) + return + } + + filterTenantIdOnly := bson.M{"tenantId": tenantId} + userDataInterface := MongoDBLibrary.RestfulAPIGetMany(userDataColl, filterTenantIdOnly) + + var userData []User + json.Unmarshal(sliceToByte(userDataInterface), &userData) + for pos, _ := range userData { + userData[pos].EncryptedPassword = "" + } + + c.JSON(http.StatusOK, userData) +} + +func GetUserByID(c *gin.Context) { + setCorsHeader(c) + + if !CheckAuth(c) { + c.JSON(http.StatusNotFound, bson.M{}) + return + } + + tenantId := c.Param("tenantId") + if len(GetTenantById(tenantId)) == 0 { + c.JSON(http.StatusNotFound, bson.M{}) + return + } + userId := c.Param("userId") + + filterUserIdOnly := bson.M{"tenantId": tenantId, "userId": userId} + userDataInterface := MongoDBLibrary.RestfulAPIGetOne(userDataColl, filterUserIdOnly) + if len(userDataInterface) == 0 { + c.JSON(http.StatusNotFound, bson.M{}) + return + } + + var userData User + json.Unmarshal(mapToByte(userDataInterface), &userData) + userData.EncryptedPassword = "" + + c.JSON(http.StatusOK, userData) +} + +func PostUserByID(c *gin.Context) { + setCorsHeader(c) + + if !CheckAuth(c) { + c.JSON(http.StatusNotFound, bson.M{}) + return + } + + tenantId := c.Param("tenantId") + if len(GetTenantById(tenantId)) == 0 { + c.JSON(http.StatusNotFound, bson.M{}) + return + } + + var userData User + if err := c.ShouldBindJSON(&userData); err != nil { + c.JSON(http.StatusBadRequest, gin.H{}) + return + } + + filterEmail := bson.M{"email": userData.Email} + userWithEmailData := MongoDBLibrary.RestfulAPIGetOne(userDataColl, filterEmail) + if len(userWithEmailData) != 0 { + logger.WebUILog.Warnln("Email already exists", userData.Email) + c.JSON(http.StatusForbidden, gin.H{}) + return + } + + userData.TenantId = tenantId + userData.UserId = uuid.Must(uuid.NewRandom()).String() + hash, _ := bcrypt.GenerateFromPassword([]byte(userData.EncryptedPassword), 12) + userData.EncryptedPassword = string(hash) + + userBsonM := toBsonM(userData) + filterUserIdOnly := bson.M{"tenantId": userData.TenantId, "userId": userData.UserId} + MongoDBLibrary.RestfulAPIPost(userDataColl, filterUserIdOnly, userBsonM) + + c.JSON(http.StatusOK, userData) +} + +func PutUserByID(c *gin.Context) { + setCorsHeader(c) + + if !CheckAuth(c) { + c.JSON(http.StatusNotFound, bson.M{}) + return + } + + tenantId := c.Param("tenantId") + if len(GetTenantById(tenantId)) == 0 { + c.JSON(http.StatusNotFound, bson.M{}) + return + } + userId := c.Param("userId") + + var newUserData User + if err := c.ShouldBindJSON(&newUserData); err != nil { + c.JSON(http.StatusBadRequest, gin.H{}) + return + } + + filterUserIdOnly := bson.M{"tenantId": tenantId, "userId": userId} + userDataInterface := MongoDBLibrary.RestfulAPIGetOne(userDataColl, filterUserIdOnly) + if len(userDataInterface) == 0 { + c.JSON(http.StatusNotFound, bson.M{}) + return + } + + var userData User + json.Unmarshal(mapToByte(userDataInterface), &userData) + + if newUserData.Email != "" && newUserData.Email != userData.Email { + filterEmail := bson.M{"email": newUserData.Email} + sameEmailInterface := MongoDBLibrary.RestfulAPIGetOne(userDataColl, filterEmail) + if len(sameEmailInterface) != 0 { + c.JSON(http.StatusBadRequest, bson.M{}) + return + } + userData.Email = newUserData.Email + } + + if newUserData.EncryptedPassword != "" { + hash, _ := bcrypt.GenerateFromPassword([]byte(newUserData.EncryptedPassword), 12) + userData.EncryptedPassword = string(hash) + } + + userBsonM := toBsonM(userData) + MongoDBLibrary.RestfulAPIPost(userDataColl, filterUserIdOnly, userBsonM) + + c.JSON(http.StatusOK, userData) +} + +func DeleteUserByID(c *gin.Context) { + setCorsHeader(c) + + if !CheckAuth(c) { + c.JSON(http.StatusNotFound, bson.M{}) + return + } + + tenantId := c.Param("tenantId") + if len(GetTenantById(tenantId)) == 0 { + c.JSON(http.StatusNotFound, bson.M{}) + return + } + userId := c.Param("userId") + + filterUserIdOnly := bson.M{"tenantId": tenantId, "userId": userId} + MongoDBLibrary.RestfulAPIDeleteOne(userDataColl, filterUserIdOnly) + + c.JSON(http.StatusOK, gin.H{}) +} + // Get all subscribers list func GetSubscribers(c *gin.Context) { setCorsHeader(c) logger.WebUILog.Infoln("Get All Subscribers List") + tokenStr := c.GetHeader("Token") + + var claims jwt.MapClaims = nil + if tokenStr != "admin" { + claims = ParseJWT(tokenStr) + } + var subsList []SubsListIE = make([]SubsListIE, 0) amDataList := MongoDBLibrary.RestfulAPIGetMany(amDataColl, bson.M{}) for _, amData := range amDataList { ueId := amData["ueId"] servingPlmnId := amData["servingPlmnId"] - tmp := SubsListIE{ - PlmnID: servingPlmnId.(string), - UeId: ueId.(string), + tenantId := amData["tenantId"] + + filterUeIdOnly := bson.M{"ueId": ueId} + authSubsDataInterface := MongoDBLibrary.RestfulAPIGetOne(authSubsDataColl, filterUeIdOnly) + + var authSubsData AuthSub + json.Unmarshal(mapToByte(authSubsDataInterface), &authSubsData) + + if tokenStr == "admin" || tenantId == claims["tenantId"].(string) { + tmp := SubsListIE{ + PlmnID: servingPlmnId.(string), + UeId: ueId.(string), + } + subsList = append(subsList, tmp) } - subsList = append(subsList, tmp) } - c.JSON(http.StatusOK, subsList) } @@ -358,6 +832,12 @@ func PostSubscriberByID(c *gin.Context) { setCorsHeader(c) logger.WebUILog.Infoln("Post One Subscriber Data") + var claims jwt.MapClaims = nil + tokenStr := c.GetHeader("Token") + if tokenStr != "admin" { + claims = ParseJWT(tokenStr) + } + var subsData SubsData if err := c.ShouldBindJSON(&subsData); err != nil { logger.WebUILog.Panic(err.Error()) @@ -369,11 +849,28 @@ func PostSubscriberByID(c *gin.Context) { filterUeIdOnly := bson.M{"ueId": ueId} filter := bson.M{"ueId": ueId, "servingPlmnId": servingPlmnId} + // Lookup same UE ID of other tenant's subscription. + if claims != nil { + authSubsDataInterface := MongoDBLibrary.RestfulAPIGetOne(authSubsDataColl, filterUeIdOnly) + if len(authSubsDataInterface) > 0 { + if authSubsDataInterface["tenantId"].(string) != claims["tenantId"].(string) { + c.JSON(http.StatusUnprocessableEntity, gin.H{}) + return + } + } + } + authSubsBsonM := toBsonM(subsData.AuthenticationSubscription) authSubsBsonM["ueId"] = ueId + if claims != nil { + authSubsBsonM["tenantId"] = claims["tenantId"].(string) + } amDataBsonM := toBsonM(subsData.AccessAndMobilitySubscriptionData) amDataBsonM["ueId"] = ueId amDataBsonM["servingPlmnId"] = servingPlmnId + if claims != nil { + amDataBsonM["tenantId"] = claims["tenantId"].(string) + } smDatasBsonA := make([]interface{}, 0, len(subsData.SessionManagementSubscriptionData)) for _, smSubsData := range subsData.SessionManagementSubscriptionData { @@ -597,7 +1094,14 @@ func GetRegisteredUEContext(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{}) return } - sendResponseToClient(c, resp) + + // Filter by tenant. + tenantId := GetTenantId(c) + if tenantId == "" { + sendResponseToClient(c, resp) + } else { + sendResponseToClientFilterTenant(c, resp, tenantId) + } } else { c.JSON(http.StatusInternalServerError, gin.H{ "cause": "No AMF Found", diff --git a/backend/WebUI/model_tenant_data.go b/backend/WebUI/model_tenant_data.go new file mode 100644 index 0000000000000000000000000000000000000000..28d44b3ddc993b74b286de9c0dc3779dc5f4df5f --- /dev/null +++ b/backend/WebUI/model_tenant_data.go @@ -0,0 +1,6 @@ +package WebUI + +type Tenant struct { + TenantId string `json:"tenantId"` + TenantName string `json:"tenantName"` +} diff --git a/backend/WebUI/model_user_data.go b/backend/WebUI/model_user_data.go new file mode 100644 index 0000000000000000000000000000000000000000..34560eafe8c45d7d99cb23ae75670de7c1343c64 --- /dev/null +++ b/backend/WebUI/model_user_data.go @@ -0,0 +1,8 @@ +package WebUI + +type User struct { + UserId string `json:"userId"` + TenantId string `json:"tenantId"` + Email string `json:"email"` + EncryptedPassword string `json:"encryptedPassword"` +} diff --git a/backend/WebUI/routers.go b/backend/WebUI/routers.go index e3fc57874774dcef3332427e7f997bf9529bcc74..1bed2554781287d9f1392d1e36f5161c5c367316 100644 --- a/backend/WebUI/routers.go +++ b/backend/WebUI/routers.go @@ -60,6 +60,90 @@ var routes = Routes{ GetSampleJSON, }, + { + "Login", + http.MethodPost, + "/login", + Login, + }, + + { + "Logout", + http.MethodPost, + "/logout", + Logout, + }, + + { + "GetTenants", + http.MethodGet, + "/tenant", + GetTenants, + }, + + { + "GetTenantByID", + http.MethodGet, + "/tenant/:tenantId", + GetTenantByID, + }, + + { + "PostTenant", + http.MethodPost, + "/tenant", + PostTenant, + }, + + { + "PutTenantByID", + http.MethodPut, + "/tenant/:tenantId", + PutTenantByID, + }, + + { + "DeleteTenantByID", + http.MethodDelete, + "/tenant/:tenantId", + DeleteTenantByID, + }, + + { + "GetUsers", + http.MethodGet, + "/tenant/:tenantId/user", + GetUsers, + }, + + { + "GetUserByID", + http.MethodGet, + "/tenant/:tenantId/user/:userId", + GetUserByID, + }, + + { + "PostUserByID", + http.MethodPost, + "/tenant/:tenantId/user", + PostUserByID, + }, + + { + "PutUserByID", + http.MethodPut, + "/tenant/:tenantId/user/:userId", + PutUserByID, + }, + + { + "DeleteUserByID", + http.MethodDelete, + "/tenant/:tenantId/user/:userId", + DeleteUserByID, + }, + { "GetSubscribers", http.MethodGet, diff --git a/frontend/src/components/SideBar/Nav.js b/frontend/src/components/SideBar/Nav.js index 61d53ec5451da320890391980c6bc8c08e704532..1effbbe562eaa5eb0573323e95be8f7fa8ae29dc 100644 --- a/frontend/src/components/SideBar/Nav.js +++ b/frontend/src/components/SideBar/Nav.js @@ -1,11 +1,25 @@ import React, {Component} from 'react'; import {Link, withRouter} from 'react-router-dom'; +import LocalStorageHelper from "../../util/LocalStorageHelper"; class Nav extends Component { state = {}; render() { let {location} = this.props; + let user = LocalStorageHelper.getUserInfo(); + let childView = ""; + if (user.accessToken === "admin") { + childView = ( + <li className={this.isPathActive('/tenants') ? 'active' : null}> + <Link to="/tenants"> + <i className="pe-7s-users"/> + <p>Tenant and User</p> + </Link> + </li> + ); + } + /* Icons: * - https://fontawesome.com/icons * - http://themes-pixeden.com/font-demos/7-stroke/ @@ -33,6 +47,8 @@ class Nav extends Component { </Link> </li> + {childView} + </ul> ); } diff --git a/frontend/src/models/Tenant.js b/frontend/src/models/Tenant.js new file mode 100644 index 0000000000000000000000000000000000000000..c860579226fb1697d003c3f972eb7ecbce7025ab --- /dev/null +++ b/frontend/src/models/Tenant.js @@ -0,0 +1,12 @@ +import Serializable from "./Serializable"; + +export default class Tenant extends Serializable{ + id = ''; + name = ""; + + constructor(id, name) { + super(); + this.id = id; + this.name = name; + } +} diff --git a/frontend/src/models/User.js b/frontend/src/models/User.js index b780188e405a1482e52a8e8170d9b415cfb403ef..a582a8eaac91abf63869e029bc8998ce436b3c62 100644 --- a/frontend/src/models/User.js +++ b/frontend/src/models/User.js @@ -4,11 +4,17 @@ export default class User extends Serializable{ username = ""; name = ""; imageUrl = ""; + accessToken = ""; + id = ''; + email = ""; - constructor(username, name) { + constructor(username, name, accessToken, id, email) { super(); this.username = username; this.name = name; this.imageUrl = 'https://cdn1.iconfinder.com/data/icons/evil-icons-user-interface/64/avatar-256.png'; + this.accessToken = accessToken; + this.id = id; + this.email = email; } } diff --git a/frontend/src/pages/Main/index.js b/frontend/src/pages/Main/index.js index af485509d5b17dc5a8cba2a5cefa91ab58d74fe3..c0478fdb2de1e68fc5fe685257a74760193ff66b 100644 --- a/frontend/src/pages/Main/index.js +++ b/frontend/src/pages/Main/index.js @@ -13,7 +13,9 @@ import SideBar from '../../components/SideBar'; import Subscribers from '../Subscribers'; import Tasks from '../Tasks'; import UEInfo from '../Dashboard'; -import UEInfoDetail from '../UEInfoDetail' +import UEInfoDetail from '../UEInfoDetail'; +import Tenants from '../Tenants'; +import Users from '../Users'; const Main = ({ mobileNavVisibility, @@ -47,6 +49,8 @@ const Main = ({ <Route exact path="/tasks" component={Tasks}/> <Route exact path="/ueinfo" component={UEInfo}/> <Route exact path="/ueinfo/:id" component={UEInfoDetail}/> + <Route exact path="/tenants" component={Tenants}/> + <Route exact path="/users/:id" component={Users}/> <Footer/> </div> diff --git a/frontend/src/pages/Tenants/TenantOverview.js b/frontend/src/pages/Tenants/TenantOverview.js new file mode 100644 index 0000000000000000000000000000000000000000..29a7b640d42d26746ca3a52b92dbc6dc1e631000 --- /dev/null +++ b/frontend/src/pages/Tenants/TenantOverview.js @@ -0,0 +1,132 @@ +import React, { Component } from 'react'; +import { Link, withRouter } from "react-router-dom"; +import { connect } from "react-redux"; +import { Button, Table } from "react-bootstrap"; +import TenantModal from "./components/TenantModal"; +import ApiHelper from "../../util/ApiHelper"; + +class TenantOverview extends Component { + state = { + tenantModalOpen: false, + tenantModalData: null, + }; + + componentDidMount() { + ApiHelper.fetchTenants().then(); + } + + openAddTenant() { + this.setState({ + tenantModalOpen: true, + tenantModalData: null, + }); + } + + /** + * @param tenantId {string} + */ + async openEditTenant(tenantId) { + const tenant = await ApiHelper.fetchTenantById(tenantId); + + this.setState({ + tenantModalOpen: true, + tenantModalData: tenant, + }); + } + + async addTenant(tenantData) { + this.setState({ tenantModalOpen: false }); + + if (!await ApiHelper.createTenant(tenantData)) { + alert("Error creating new tenant"); + } + ApiHelper.fetchTenants().then(); + } + + /** + * @param tenantData + */ + async updateTenant(tenantData) { + this.setState({ tenantModalOpen: false }); + + const result = await ApiHelper.updateTenant(tenantData); + + if (!result) { + alert("Error updating tenant: " + tenantData["ueId"]); + } + ApiHelper.fetchTenants().then(); + } + + /** + * @param tenant {Tenant} + */ + async deleteTenant(tenant) { + if (!window.confirm(`Delete tenant ${tenant.id}?`)) + return; + + const result = await ApiHelper.deleteTenant(tenant.id); + ApiHelper.fetchTenants().then(); + if (!result) { + alert("Error deleting tenant: " + tenant.id); + } + } + + render() { + return ( + <div className="container-fluid"> + <div className="row"> + <div className="col-md-12"> + <div className="card"> + <div className="header subscribers__header"> + <h4>Tenants</h4> + <Button bsStyle={"primary"} className="subscribers__button" + onClick={this.openAddTenant.bind(this)}> + New Tenant + </Button> + </div> + <div className="content subscribers__content"> + <Table className="subscribers__table" striped bordered condensed hover> + <thead> + <tr> + <th style={{ width: 400 }}>Tenant ID</th> + <th colSpan={2}>Tenant Name</th> + </tr> + </thead> + <tbody> + {this.props.tenants.map(tenant => ( + <tr key={tenant.id}> + <td>{tenant.id}</td> + <td><font color="blue"><u><Link to={"/users/"+tenant.id}>{tenant.name}</Link></u></font></td> + <td style={{ textAlign: 'center' }}> + <Button variant="danger" onClick={this.deleteTenant.bind(this, tenant)}>Delete</Button> + + <Button variant="info" onClick={this.openEditTenant.bind(this, tenant.id)}>Modify</Button> + </td> + </tr> + ))} + </tbody> + </Table> + + <p> </p><p> </p> + <p> </p><p> </p> + <p> </p><p> </p> + </div> + </div> + </div> + </div> + + <TenantModal open={this.state.tenantModalOpen} + setOpen={val => this.setState({ tenantModalOpen: val })} + tenant={this.state.tenantModalData} + onModify={this.updateTenant.bind(this)} + onSubmit={this.addTenant.bind(this)} /> + </div> + ); + } +} + +const mapStateToProps = state => ({ + tenants: state.tenant.tenants, +}); + +export default withRouter(connect(mapStateToProps)(TenantOverview)); diff --git a/frontend/src/pages/Tenants/components/TenantModal.js b/frontend/src/pages/Tenants/components/TenantModal.js new file mode 100644 index 0000000000000000000000000000000000000000..6a3dd63af927622e2107ea226dca558f19769dfd --- /dev/null +++ b/frontend/src/pages/Tenants/components/TenantModal.js @@ -0,0 +1,133 @@ +import React, { Component } from 'react'; +import { Modal } from "react-bootstrap"; +import Form from "react-jsonschema-form"; +import PropTypes from 'prop-types'; + +class TenantModal extends Component { + static propTypes = { + open: PropTypes.bool.isRequired, + setOpen: PropTypes.func.isRequired, + tenant: PropTypes.object, + onModify: PropTypes.func.isRequired, + onSubmit: PropTypes.func.isRequired, + }; + + state = { + editMode: false, + formData: undefined, + // for force re-rendering json form + rerenderCounter: 0, + }; + + state = { + formData: undefined, + editMode: false, + // for force re-rendering json form + rerenderCounter: 0, + }; + + schema = { + // title: "A registration form", + // "description": "A simple form example.", + type: "object", + required: [ + "tenantName", + ], + properties: { + tenantId: { + type: "string", + title: "Tenant ID", + pattern: "^[0-9a-zA-Z-]*$", + default: "", + readOnly: true, + }, + tenantName: { + type: "string", + title: "Tenant Name", + default: "", + }, + }, + }; + + componentDidUpdate(prevProps, prevState, snapshot) { + if (prevProps !== this.props) { + this.setState({ editMode: !!this.props.tenant }); + + if (this.props.tenant) { + const tenant = this.props.tenant; + + let formData = { + tenantId: tenant['tenantId'], + tenantName: tenant['tenantName'], + }; + + this.updateFormData(formData).then(); + } else { + let formData = { + tenantId: "", + tenantName: "", + }; + this.updateFormData(formData).then(); + } + } + } + + async onChange(data) { + const lastData = this.state.formData; + + if (lastData && lastData.tenantId === undefined) + lastData.tenantId = ""; + } + + async updateFormData(newData) { + // Workaround for bug: https://github.com/rjsf-team/react-jsonschema-form/issues/758 + await this.setState({ rerenderCounter: this.state.rerenderCounter + 1 }); + await this.setState({ + rerenderCounter: this.state.rerenderCounter + 1, + formData: newData, + }); + } + + onSubmitClick(result) { + const formData = result.formData; + + let tenantData = { + "tenantId": formData["tenantId"], + "tenantName": formData["tenantName"] + }; + + if(this.state.editMode) { + this.props.onModify(tenantData); + } else { + this.props.onSubmit(tenantData); + } + } + + render() { + return ( + <Modal + show={this.props.open} + className={"fields__edit-modal theme-light"} + backdrop={"static"} + onHide={this.props.setOpen.bind(this, false)}> + <Modal.Header closeButton> + <Modal.Title id="example-modal-sizes-title-lg"> + {this.state.editMode ? "Edit Tenant" : "New Tenant"} + </Modal.Title> + </Modal.Header> + + <Modal.Body> + {this.state.rerenderCounter % 2 === 0 && + <Form schema={this.schema} + formData={this.state.formData} + onChange={this.onChange.bind(this)} + onSubmit={this.onSubmitClick.bind(this)} /> + } + </Modal.Body> + </Modal> + ); + + } +} + +export default TenantModal; diff --git a/frontend/src/pages/Tenants/index.js b/frontend/src/pages/Tenants/index.js new file mode 100644 index 0000000000000000000000000000000000000000..9511e0dae9648184e339c24b81b840f43e8562f8 --- /dev/null +++ b/frontend/src/pages/Tenants/index.js @@ -0,0 +1,11 @@ +import React from 'react'; +import {Route} from 'react-router-dom'; +import TenantOverview from "./TenantOverview"; + +const Tenants = ({match}) => ( + <div className="content"> + <Route exact path={`${match.url}/`} component={TenantOverview} /> + </div> +); + +export default Tenants; diff --git a/frontend/src/pages/Users/UserOverview.js b/frontend/src/pages/Users/UserOverview.js new file mode 100644 index 0000000000000000000000000000000000000000..6422d992e8bd03af33a063421dadec0177fe0f1a --- /dev/null +++ b/frontend/src/pages/Users/UserOverview.js @@ -0,0 +1,140 @@ +import React, { Component } from 'react'; +import { withRouter } from "react-router-dom"; +import { connect } from "react-redux"; +import { Button, Table } from "react-bootstrap"; +import UserModal from "./components/UserModal"; +import ApiHelper from "../../util/ApiHelper"; + +class UserOverview extends Component { + state = { + userModalOpen: false, + userModalData: null, + }; + + async componentDidMount() { + const tenantId = this.props.match.url.replace(/^.*[\\\/]/, ''); + + ApiHelper.fetchUsers(tenantId).then(); + + const tenant = await ApiHelper.fetchTenantById(tenantId); + this.setState({ + tenantId: tenantId, + tenantName: tenant.tenantName, + }); + } + + openAddUser() { + this.setState({ + userModalOpen: true, + userModalData: null, + }); + } + + /** + * @param userId {string} + */ + async openEditUser(userId) { + const user = await ApiHelper.fetchUserById(this.state.tenantId, userId); + + this.setState({ + userModalOpen: true, + userModalData: user, + }); + } + + async addUser(userData) { + this.setState({ userModalOpen: false }); + + if (!await ApiHelper.createUser(this.state.tenantId, userData)) { + alert("Error creating new user"); + } + ApiHelper.fetchUsers(this.state.tenantId).then(); + } + + /** + * @param userData + */ + async updateUser(userData) { + this.setState({ userModalOpen: false }); + + const result = await ApiHelper.updateUser(this.state.tenantId, userData.userId, userData); + + if (!result) { + alert("Error updating user: " + userData["userId"]); + } + ApiHelper.fetchUsers(this.state.tenantId).then(); + } + + /** + * @param user {User} + */ + async deleteUser(user) { + if (!window.confirm(`Delete user ${user.id}?`)) + return; + + const result = await ApiHelper.deleteUser(this.state.tenantId, user.id); + ApiHelper.fetchUsers(this.state.tenantId).then(); + if (!result) { + alert("Error deleting user: " + user.id); + } + } + + render() { + return ( + <div className="container-fluid"> + <div className="row"> + <div className="col-md-12"> + <div className="card"> + <div className="header subscribers__header"> + <h4>Users ({this.state.tenantName})</h4> + <Button bsStyle={"primary"} className="subscribers__button" + onClick={this.openAddUser.bind(this)}> + New User + </Button> + </div> + <div className="content subscribers__content"> + <Table className="subscribers__table" striped bordered condensed hover> + <thead> + <tr> + <th style={{ width: 400 }}>User ID</th> + <th colSpan={2}>User Email</th> + </tr> + </thead> + <tbody> + {this.props.users.map(user => ( + <tr key={user.id}> + <td>{user.id}</td> + <td>{user.email}</td> + <td style={{ textAlign: 'center' }}> + <Button variant="danger" onClick={this.deleteUser.bind(this, user)}>Delete</Button> + + <Button variant="info" onClick={this.openEditUser.bind(this, user.id)}>Modify</Button> + </td> + </tr> + ))} + </tbody> + </Table> + + <p> </p><p> </p> + <p> </p><p> </p> + <p> </p><p> </p> + </div> + </div> + </div> + </div> + + <UserModal open={this.state.userModalOpen} + setOpen={val => this.setState({ userModalOpen: val })} + user={this.state.userModalData} + onModify={this.updateUser.bind(this)} + onSubmit={this.addUser.bind(this)} /> + </div> + ); + } +} + +const mapStateToProps = state => ({ + users: state.user.users, +}); + +export default withRouter(connect(mapStateToProps)(UserOverview)); diff --git a/frontend/src/pages/Users/components/UserModal.js b/frontend/src/pages/Users/components/UserModal.js new file mode 100644 index 0000000000000000000000000000000000000000..1d42d7f548ea7a13eb355a7f1fcd2815ce47e121 --- /dev/null +++ b/frontend/src/pages/Users/components/UserModal.js @@ -0,0 +1,141 @@ +import React, { Component } from 'react'; +import { Modal } from "react-bootstrap"; +import Form from "react-jsonschema-form"; +import PropTypes from 'prop-types'; + +class UserModal extends Component { + static propTypes = { + open: PropTypes.bool.isRequired, + setOpen: PropTypes.func.isRequired, + user: PropTypes.object, + onModify: PropTypes.func.isRequired, + onSubmit: PropTypes.func.isRequired, + }; + + state = { + editMode: false, + formData: undefined, + // for force re-rendering json form + rerenderCounter: 0, + }; + + state = { + formData: undefined, + editMode: false, + // for force re-rendering json form + rerenderCounter: 0, + }; + + schema = { + // title: "A registration form", + // "description": "A simple form example.", + type: "object", + required: [ + "email", + ], + properties: { + userId: { + type: "string", + title: "User ID", + pattern: "^[0-9a-zA-Z-]*$", + default: "", + readOnly: true, + }, + email: { + type: "string", + title: "User Email", + default: "", + }, + password: { + type: "string", + title: "Password", + default: "", + }, + }, + }; + + componentDidUpdate(prevProps, prevState, snapshot) { + if (prevProps !== this.props) { + this.setState({ editMode: !!this.props.user }); + + if (this.props.user) { + const user = this.props.user; + + let formData = { + userId: user['userId'], + email: user['email'], + password: user['password'], + }; + + this.updateFormData(formData).then(); + } else { + let formData = { + userId: "", + email: "", + password: "", + }; + this.updateFormData(formData).then(); + } + } + } + + async onChange(data) { + const lastData = this.state.formData; + + if (lastData && lastData.userId === undefined) + lastData.userId = ""; + } + + async updateFormData(newData) { + // Workaround for bug: https://github.com/rjsf-team/react-jsonschema-form/issues/758 + await this.setState({ rerenderCounter: this.state.rerenderCounter + 1 }); + await this.setState({ + rerenderCounter: this.state.rerenderCounter + 1, + formData: newData, + }); + } + + onSubmitClick(result) { + const formData = result.formData; + + let userData = { + "userId": formData["userId"], + "email": formData["email"], + "password": formData["password"] + }; + + if(this.state.editMode) { + this.props.onModify(userData); + } else { + this.props.onSubmit(userData); + } + } + + render() { + return ( + <Modal + show={this.props.open} + className={"fields__edit-modal theme-light"} + backdrop={"static"} + onHide={this.props.setOpen.bind(this, false)}> + <Modal.Header closeButton> + <Modal.Title id="example-modal-sizes-title-lg"> + {this.state.editMode ? "Edit User" : "New User"} + </Modal.Title> + </Modal.Header> + + <Modal.Body> + {this.state.rerenderCounter % 2 === 0 && + <Form schema={this.schema} + formData={this.state.formData} + onChange={this.onChange.bind(this)} + onSubmit={this.onSubmitClick.bind(this)} /> + } + </Modal.Body> + </Modal> + ); + + } +} + +export default UserModal; diff --git a/frontend/src/pages/Users/index.js b/frontend/src/pages/Users/index.js new file mode 100644 index 0000000000000000000000000000000000000000..6308c8f46a84ead20c8d70856c242dc0e9d09c16 --- /dev/null +++ b/frontend/src/pages/Users/index.js @@ -0,0 +1,11 @@ +import React from 'react'; +import {Route} from 'react-router-dom'; +import UserOverview from "./UserOverview"; + +const Users = ({match}) => ( + <div className="content"> + <Route exact path={`${match.url}/`} component={UserOverview} /> + </div> +); + +export default Users; diff --git a/frontend/src/redux/actions/tenantActions.js b/frontend/src/redux/actions/tenantActions.js new file mode 100644 index 0000000000000000000000000000000000000000..3ad6348594ee1b59ff859c376fea90ca8f966ca4 --- /dev/null +++ b/frontend/src/redux/actions/tenantActions.js @@ -0,0 +1,11 @@ + +export default class tenantActions { + static SET_TENANTS = 'TENANT/SET_TENANTS'; + + static setTenants(tenants) { + return { + type: this.SET_TENANTS, + tenants: tenants, + }; + } +} diff --git a/frontend/src/redux/actions/userActions.js b/frontend/src/redux/actions/userActions.js new file mode 100644 index 0000000000000000000000000000000000000000..6e8ea4c21fc56d9ff23ad9ae02e7d00823b80b70 --- /dev/null +++ b/frontend/src/redux/actions/userActions.js @@ -0,0 +1,11 @@ + +export default class userActions { + static SET_USERS = 'USER/SET_USERS'; + + static setUsers(users) { + return { + type: this.SET_USERS, + users: users, + }; + } +} diff --git a/frontend/src/redux/reducers/index.js b/frontend/src/redux/reducers/index.js index bef8ba6278fc1d9ba36cebb7b9ee2676528b98c5..b7e91bff53fb9243698550ff9d33bbae84a1ad76 100644 --- a/frontend/src/redux/reducers/index.js +++ b/frontend/src/redux/reducers/index.js @@ -3,11 +3,15 @@ import auth from './auth'; import layout from './layout'; import subscriber from "./subscriber"; import ueinfo from "./ueinfo"; +import tenant from "./tenant"; +import user from "./user"; export default { auth, layout, subscriber, ueinfo, + tenant, + user, form: formReducer, }; diff --git a/frontend/src/redux/reducers/tenant.js b/frontend/src/redux/reducers/tenant.js new file mode 100644 index 0000000000000000000000000000000000000000..c5e9be401cbd7144cfed5ac728647eab5f13107c --- /dev/null +++ b/frontend/src/redux/reducers/tenant.js @@ -0,0 +1,26 @@ +import actions from '../actions/tenantActions'; + +const initialState = { + tenants: [], + tenantsMap: {} +}; + +export default function reducer(state = initialState, action) { + let nextState = {...state}; + + switch (action.type) { + case actions.SET_TENANTS: + nextState.tenants = action.tenants; + nextState.tenantsMap = createTenantsMap(action.tenants); + return nextState; + + default: + return state; + } +} + +function createTenantsMap(tenants) { + let tenantsMap = {}; + tenants.forEach(tenants => tenantsMap[tenants['id']] = tenants); + return tenantsMap; +} diff --git a/frontend/src/redux/reducers/user.js b/frontend/src/redux/reducers/user.js new file mode 100644 index 0000000000000000000000000000000000000000..ad22f8ec2233c94cbc34ea6268b0fa29d454e940 --- /dev/null +++ b/frontend/src/redux/reducers/user.js @@ -0,0 +1,26 @@ +import actions from '../actions/userActions'; + +const initialState = { + users: [], + usersMap: {} +}; + +export default function reducer(state = initialState, action) { + let nextState = {...state}; + + switch (action.type) { + case actions.SET_USERS: + nextState.users = action.users; + nextState.usersMap = createUsersMap(action.users); + return nextState; + + default: + return state; + } +} + +function createUsersMap(users) { + let usersMap = {}; + users.forEach(users => usersMap[users['id']] = users); + return usersMap; +} diff --git a/frontend/src/util/ApiHelper.js b/frontend/src/util/ApiHelper.js index 5dc35bae53b714d3ca84f15cc8f108cdc5d66364..b1a0a4b43f57ef1c7f2f1a3793ff4cd006502582 100644 --- a/frontend/src/util/ApiHelper.js +++ b/frontend/src/util/ApiHelper.js @@ -2,11 +2,19 @@ import Http from './Http'; import {store} from '../index'; import subscriberActions from "../redux/actions/subscriberActions"; import Subscriber from "../models/Subscriber"; +import tenantActions from "../redux/actions/tenantActions"; +import Tenant from "../models/Tenant"; +import userActions from "../redux/actions/userActions"; +import User from "../models/User"; +import axios from 'axios'; +import LocalStorageHelper from "./LocalStorageHelper"; class ApiHelper { static async fetchSubscribers() { try { + let user = LocalStorageHelper.getUserInfo(); + axios.defaults.headers.common['Token'] = user.accessToken; let response = await Http.get('subscriber'); if (response.status === 200 && response.data) { const subscribers = response.data.map(val => new Subscriber(val['ueId'], val['plmnID'])); @@ -33,6 +41,8 @@ class ApiHelper { static async createSubscriber(subscriberData) { try { + let user = LocalStorageHelper.getUserInfo(); + axios.defaults.headers.common['Token'] = user.accessToken; let response = await Http.post( `subscriber/${subscriberData["ueId"]}/${subscriberData["plmnID"]}`, subscriberData); if (response.status === 201) @@ -67,7 +77,171 @@ class ApiHelper { } return false; - } + } + + static async login(loginRequest) { + console.log("login"); + try { + let response = await Http.post(`login`, loginRequest); + return response; + } catch (error) { + console.error(error); + } + return false; + } + + static async fetchTenants() { + try { + store.dispatch(tenantActions.setTenants([])); + let user = LocalStorageHelper.getUserInfo(); + axios.defaults.headers.common['Token'] = user.accessToken; + let response = await Http.get('tenant'); + if (response.status === 200 && response.data) { + const tenants = response.data.map(val => new Tenant(val['tenantId'], val['tenantName'])); + store.dispatch(tenantActions.setTenants(tenants)); + return true; + } + } catch (error) { + } + + return false; + } + + static async fetchTenantById(id) { + try { + let user = LocalStorageHelper.getUserInfo(); + axios.defaults.headers.common['Token'] = user.accessToken; + let response = await Http.get(`tenant/${id}`); + if (response.status === 200 && response.data) { + return response.data; + } + } catch (error) { + } + + return false; + } + + static async createTenant(tenantData) { + try { + let user = LocalStorageHelper.getUserInfo(); + axios.defaults.headers.common['Token'] = user.accessToken; + let response = await Http.post( + 'tenant', tenantData); + if (response.status === 200) + return true; + } catch (error) { + console.error(error); + } + + return false; + } + + static async updateTenant(tenantData) { + try { + let user = LocalStorageHelper.getUserInfo(); + axios.defaults.headers.common['Token'] = user.accessToken; + let response = await Http.put( + `tenant/${tenantData["tenantId"]}`, tenantData); + if (response.status === 200) + return true; + } catch (error) { + console.error(error); + } + + return false; + } + + static async deleteTenant(id) { + try { + let user = LocalStorageHelper.getUserInfo(); + axios.defaults.headers.common['Token'] = user.accessToken; + let response = await Http.delete(`tenant/${id}`); + if (response.status === 200) + return true; + } catch (error) { + console.error(error); + } + + return false; + } + + static async fetchUsers(tenantId) { + try { + store.dispatch(userActions.setUsers([])); + let user = LocalStorageHelper.getUserInfo(); + axios.defaults.headers.common['Token'] = user.accessToken; + let response = await Http.get(`tenant/${tenantId}/user`); + if (response.status === 200 && response.data) { + const users = response.data.map(val => new User('', '', '', val['userId'], val['email'])); + store.dispatch(userActions.setUsers(users)); + return true; + } + } catch (error) { + } + + return false; + } + + static async fetchUserById(tenantId, id) { + try { + let user = LocalStorageHelper.getUserInfo(); + axios.defaults.headers.common['Token'] = user.accessToken; + let response = await Http.get(`tenant/${tenantId}/user/${id}`); + if (response.status === 200 && response.data) { + return response.data; + } + } catch (error) { + } + + return false; + } + + static async createUser(tenantId, userData) { + try { + let user = LocalStorageHelper.getUserInfo(); + axios.defaults.headers.common['Token'] = user.accessToken; + userData['encryptedPassword'] = userData['password']; + let response = await Http.post( + `tenant/${tenantId}/user`, userData); + if (response.status === 200) + return true; + } catch (error) { + console.error(error); + } + + return false; + } + + static async updateUser(tenantId, userId, userData) { + try { + let user = LocalStorageHelper.getUserInfo(); + axios.defaults.headers.common['Token'] = user.accessToken; + userData['encryptedPassword'] = userData['password']; + let response = await Http.put( + `tenant/${tenantId}/user/${userId}`, userData); + if (response.status === 200) + return true; + } catch (error) { + console.error(error); + } + + return false; + } + + static async deleteUser(tenantId, id) { + try { + let user = LocalStorageHelper.getUserInfo(); + axios.defaults.headers.common['Token'] = user.accessToken; + let response = await Http.delete(`tenant/${tenantId}/user/${id}`); + if (response.status === 200) + return true; + } catch (error) { + console.error(error); + } + + return false; + } + } export default ApiHelper; diff --git a/frontend/src/util/AuthHelper.js b/frontend/src/util/AuthHelper.js index ade18d87cb1866409510049339ae610c483de016..4f902d896083a0607777c74e95f788d578511f52 100644 --- a/frontend/src/util/AuthHelper.js +++ b/frontend/src/util/AuthHelper.js @@ -2,6 +2,8 @@ import {store} from '../index'; import authActions from '../redux/actions/authActions'; import config from '../config/config'; import User from "../models/User"; +import ApiHelper from "./ApiHelper"; +import LocalStorageHelper from "./LocalStorageHelper"; export default class AuthHelper { @@ -13,11 +15,23 @@ export default class AuthHelper { */ static async login(username, password) { if (username === config.USERNAME && password === config.PASSWORD) { - let user = new User(username, "System Administrator"); + let user = new User(username, "System Administrator", "admin"); + LocalStorageHelper.setUserInfo(user); store.dispatch(authActions.setUser(user)); return true; } else { - return false; + let response = await ApiHelper.login({username: username, password: password}); + if (response === undefined) { + return false; + } + if (response.status === 200) { + let user = new User(username, "User", response.data.access_token); + LocalStorageHelper.setUserInfo(user); + store.dispatch(authActions.setUser(user)); + return true; + } else { + return false; + } } } diff --git a/frontend/src/util/LocalStorageHelper.js b/frontend/src/util/LocalStorageHelper.js index 4b0b77ab5ae8d20305a690a743763723ca8bf4ad..27988eff937abe8c2ee4a9d9fc5c7344cfc931df 100644 --- a/frontend/src/util/LocalStorageHelper.js +++ b/frontend/src/util/LocalStorageHelper.js @@ -14,6 +14,7 @@ export default class LocalStorageHelper { */ static getUserInfo() { let json = localStorage.getItem('user_info'); - return json === null ? null : ApiTokens.deserialize(json); + // return json === null ? null : ApiTokens.deserialize(json); + return json === null ? null : User.deserialize(json); } } diff --git a/frontend/src/util/UEInfoApiHelper.js b/frontend/src/util/UEInfoApiHelper.js index c6b01f04de13c430196473e0f8dadd5e38ba7b4e..67b06c050ade99324e098b4e1e7e6db19e6d5f8b 100644 --- a/frontend/src/util/UEInfoApiHelper.js +++ b/frontend/src/util/UEInfoApiHelper.js @@ -2,6 +2,8 @@ import Http from './Http'; import {store} from '../index'; import ueinfoActions from "../redux/actions/ueinfoActions"; import UEInfo from "../models/UEInfo"; +import axios from 'axios'; +import LocalStorageHelper from "./LocalStorageHelper"; class UeInfoApiHelper { @@ -11,6 +13,8 @@ class UeInfoApiHelper { try { let url = "registered-ue-context" // console.log("Making request to ", url, " ....") + let user = LocalStorageHelper.getUserInfo(); + axios.defaults.headers.common['Token'] = user.accessToken; let response = await Http.get(url); if (response.status === 200) { let registered_users = []; @@ -20,7 +24,7 @@ class UeInfoApiHelper { ); store.dispatch(ueinfoActions.setRegisteredUE(registered_users)); } else { - store.dispatch(ueinfoActions.unsetRegisteredUEError()); + store.dispatch(ueinfoActions.setRegisteredUE(registered_users)); } return true; } else { @@ -55,6 +59,8 @@ class UeInfoApiHelper { let url = `registered-ue-context/${supi}` // console.log("Making request to ", url, " ....") + let user = LocalStorageHelper.getUserInfo(); + axios.defaults.headers.common['Token'] = user.accessToken; let response = await Http.get(url); if (response.status === 200 && response.data) { //To do: implement set rgistered ue action @@ -84,6 +90,8 @@ class UeInfoApiHelper { let url = `ue-pdu-session-info/${smContextRef}` // console.log("Making request to ", url, " ....") + let user = LocalStorageHelper.getUserInfo(); + axios.defaults.headers.common['Token'] = user.accessToken; let response = await Http.get(url); if (response.status === 200 && response.data) { //To do: implement set rgistered ue action diff --git a/go.mod b/go.mod index e91bdbb9537d4d68225dc87cb3dc92e25a4d0f2d..232f667c1cbe1b8f7efe0ad110bf52084927c3ab 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.14 require ( github.com/antonfisher/nested-logrus-formatter v1.3.0 github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect + github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/free5gc/MongoDBLibrary v1.0.0 github.com/free5gc/logger_conf v1.0.0 github.com/free5gc/logger_util v1.0.0 @@ -13,10 +14,12 @@ require ( github.com/free5gc/version v1.0.0 github.com/gin-contrib/cors v1.3.1 github.com/gin-gonic/gin v1.6.3 + github.com/google/uuid v1.3.0 github.com/mitchellh/mapstructure v1.4.0 github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sirupsen/logrus v1.7.0 github.com/urfave/cli v1.22.5 go.mongodb.org/mongo-driver v1.4.4 + golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 140ab64508a4010f7212bdf01b2412247fdf3666..a76e669fb3d8d0a14384b88323db5b487a73df7c 100644 --- a/go.sum +++ b/go.sum @@ -171,6 +171,8 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=