agi.user.go 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. package agi
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "net/http"
  7. "os"
  8. "path/filepath"
  9. "sync"
  10. "github.com/robertkrimen/otto"
  11. "imuslab.com/arozos/mod/agi/static"
  12. "imuslab.com/arozos/mod/filesystem"
  13. "imuslab.com/arozos/mod/filesystem/arozfs"
  14. "imuslab.com/arozos/mod/info/logger"
  15. user "imuslab.com/arozos/mod/user"
  16. )
  17. // Inject user based functions into the virtual machine
  18. // Note that the fsh might be nil and scriptPath must be real path of script being executed
  19. // **Use local file system check if fsh == nil**
  20. //
  21. // Returns a teardown closure that releases every resource libraries registered
  22. // during this VM's lifetime (see static.AgiLibInjectionPayload.RegisterCleanup).
  23. // Callers MUST defer it - it is safe to call more than once.
  24. func (g *Gateway) injectUserFunctions(vm *otto.Otto, fsh *filesystem.FileSystemHandler, scriptPath string, scriptScope string, u *user.User, w http.ResponseWriter, r *http.Request) func() {
  25. //Teardown closures registered by loadable libraries for this VM.
  26. //Guarded by a mutex as a library may spawn goroutines that register late.
  27. var cleanupMutex sync.Mutex
  28. cleanups := []func(){}
  29. registerCleanup := func(cleanup func()) {
  30. cleanupMutex.Lock()
  31. defer cleanupMutex.Unlock()
  32. cleanups = append(cleanups, cleanup)
  33. }
  34. //Run every registered teardown once, in reverse registration order.
  35. runCleanups := func() {
  36. cleanupMutex.Lock()
  37. pending := cleanups
  38. cleanups = nil
  39. cleanupMutex.Unlock()
  40. for i := len(pending) - 1; i >= 0; i-- {
  41. func(cleanup func()) {
  42. //A panicking teardown must not prevent the remaining ones from running
  43. defer func() {
  44. if caught := recover(); caught != nil {
  45. logger.PrintAndLog("Agi", fmt.Sprint("Library cleanup panicked: ", caught), nil)
  46. }
  47. }()
  48. cleanup()
  49. }(pending[i])
  50. }
  51. }
  52. username := u.Username
  53. vm.Set("USERNAME", username)
  54. vm.Set("USERICON", u.GetUserIcon())
  55. vm.Set("USERQUOTA_TOTAL", u.StorageQuota.TotalStorageQuota)
  56. vm.Set("USERQUOTA_USED", u.StorageQuota.UsedStorageQuota)
  57. vm.Set("USER_VROOTS", u.GetAllAccessibleFileSystemHandler())
  58. vm.Set("USER_MODULES", u.GetUserAccessibleModules())
  59. //File system and path related
  60. vm.Set("decodeVirtualPath", func(call otto.FunctionCall) otto.Value {
  61. logger.PrintAndLog("Agi", "Call to deprecated function decodeVirtualPath", nil)
  62. return otto.FalseValue()
  63. })
  64. vm.Set("decodeAbsoluteVirtualPath", func(call otto.FunctionCall) otto.Value {
  65. logger.PrintAndLog("Agi", "Call to deprecated function decodeAbsoluteVirtualPath", nil)
  66. return otto.FalseValue()
  67. })
  68. vm.Set("encodeRealPath", func(call otto.FunctionCall) otto.Value {
  69. logger.PrintAndLog("Agi", "Call to deprecated function encodeRealPath", nil)
  70. return otto.FalseValue()
  71. })
  72. //Check if a given virtual path is readonly
  73. vm.Set("pathCanWrite", func(call otto.FunctionCall) otto.Value {
  74. vpath, _ := call.Argument(0).ToString()
  75. if u.CanWrite(vpath) {
  76. return otto.TrueValue()
  77. } else {
  78. return otto.FalseValue()
  79. }
  80. })
  81. //Permission related
  82. vm.Set("getUserPermissionGroup", func(call otto.FunctionCall) otto.Value {
  83. groupinfo := u.GetUserPermissionGroup()
  84. jsonString, _ := json.Marshal(groupinfo)
  85. reply, _ := vm.ToValue(string(jsonString))
  86. return reply
  87. })
  88. vm.Set("userIsAdmin", func(call otto.FunctionCall) otto.Value {
  89. reply, _ := vm.ToValue(u.IsAdmin())
  90. return reply
  91. })
  92. //User Account Related
  93. /*
  94. userExists(username);
  95. */
  96. vm.Set("userExists", func(call otto.FunctionCall) otto.Value {
  97. if u.IsAdmin() {
  98. //Get username from function paramter
  99. username, err := call.Argument(0).ToString()
  100. if err != nil || username == "undefined" {
  101. g.RaiseError(errors.New("username is undefined"))
  102. reply, _ := vm.ToValue(nil)
  103. return reply
  104. }
  105. //Check if user exists
  106. userExists := u.Parent().GetAuthAgent().UserExists(username)
  107. if userExists {
  108. return otto.TrueValue()
  109. } else {
  110. return otto.FalseValue()
  111. }
  112. } else {
  113. g.RaiseError(errors.New("Permission Denied: userExists require admin permission"))
  114. return otto.FalseValue()
  115. }
  116. })
  117. /*
  118. createUser(username, password, defaultGroup);
  119. */
  120. vm.Set("createUser", func(call otto.FunctionCall) otto.Value {
  121. if u.IsAdmin() {
  122. //Ok. Create user base on given information
  123. username, err := call.Argument(0).ToString()
  124. if err != nil || username == "undefined" {
  125. g.RaiseError(errors.New("username is undefined"))
  126. reply, _ := vm.ToValue(false)
  127. return reply
  128. }
  129. password, err := call.Argument(1).ToString()
  130. if err != nil || password == "undefined" {
  131. g.RaiseError(errors.New("password is undefined"))
  132. reply, _ := vm.ToValue(false)
  133. return reply
  134. }
  135. defaultGroup, err := call.Argument(2).ToString()
  136. if err != nil || defaultGroup == "undefined" {
  137. g.RaiseError(errors.New("defaultGroup is undefined"))
  138. reply, _ := vm.ToValue(false)
  139. return reply
  140. }
  141. //Check if username already used
  142. userExists := u.Parent().GetAuthAgent().UserExists(username)
  143. if userExists {
  144. g.RaiseError(errors.New("Username already exists"))
  145. reply, _ := vm.ToValue(false)
  146. return reply
  147. }
  148. //Check if the given permission group exists
  149. groupExists := u.Parent().GetPermissionHandler().GroupExists(defaultGroup)
  150. if !groupExists {
  151. g.RaiseError(errors.New(defaultGroup + " user-group not exists"))
  152. reply, _ := vm.ToValue(false)
  153. return reply
  154. }
  155. //Create the user
  156. err = u.Parent().GetAuthAgent().CreateUserAccount(username, password, []string{defaultGroup})
  157. if err != nil {
  158. g.RaiseError(errors.New("User creation failed: " + err.Error()))
  159. reply, _ := vm.ToValue(false)
  160. return reply
  161. }
  162. return otto.TrueValue()
  163. } else {
  164. g.RaiseError(errors.New("Permission Denied: createUser require admin permission"))
  165. return otto.FalseValue()
  166. }
  167. })
  168. vm.Set("editUser", func(call otto.FunctionCall) otto.Value {
  169. if u.IsAdmin() {
  170. } else {
  171. g.RaiseError(errors.New("Permission Denied: editUser require admin permission"))
  172. return otto.FalseValue()
  173. }
  174. //libname, err := call.Argument(0).ToString()
  175. return otto.FalseValue()
  176. })
  177. /*
  178. removeUser(username)
  179. */
  180. vm.Set("removeUser", func(call otto.FunctionCall) otto.Value {
  181. if u.IsAdmin() {
  182. //Get username from function paramters
  183. username, err := call.Argument(0).ToString()
  184. if err != nil || username == "undefined" {
  185. g.RaiseError(errors.New("username is undefined"))
  186. reply, _ := vm.ToValue(false)
  187. return reply
  188. }
  189. //Check if the user exists
  190. userExists := u.Parent().GetAuthAgent().UserExists(username)
  191. if !userExists {
  192. g.RaiseError(errors.New(username + " not exists"))
  193. reply, _ := vm.ToValue(false)
  194. return reply
  195. }
  196. //User exists. Remove it from the system
  197. err = u.Parent().GetAuthAgent().UnregisterUser(username)
  198. if err != nil {
  199. g.RaiseError(errors.New("User removal failed: " + err.Error()))
  200. reply, _ := vm.ToValue(false)
  201. return reply
  202. }
  203. return otto.TrueValue()
  204. } else {
  205. g.RaiseError(errors.New("Permission Denied: removeUser require admin permission"))
  206. return otto.FalseValue()
  207. }
  208. })
  209. //Allow real time library includsion into the virtual machine
  210. vm.Set("requirelib", func(call otto.FunctionCall) otto.Value {
  211. libname, err := call.Argument(0).ToString()
  212. if err != nil {
  213. g.RaiseError(err)
  214. reply, _ := vm.ToValue(nil)
  215. return reply
  216. }
  217. //Handle special case on high level libraries
  218. if libname == "websocket" && w != nil && r != nil {
  219. g.injectWebSocketFunctions(vm, u, w, r)
  220. return otto.TrueValue()
  221. } else {
  222. //Check if the library name exists. If yes, run the initiation script on the vm
  223. if entryPoint, ok := g.LoadedAGILibrary[libname]; ok {
  224. entryPoint(&static.AgiLibInjectionPayload{
  225. VM: vm,
  226. User: u,
  227. ScriptFsh: fsh,
  228. ScriptPath: scriptPath,
  229. Writer: w,
  230. Request: r,
  231. RegisterCleanup: registerCleanup,
  232. })
  233. return otto.TrueValue()
  234. } else {
  235. //Lib not exists
  236. logger.PrintAndLog("Agi", "Lib not found: "+libname, nil)
  237. return otto.FalseValue()
  238. }
  239. }
  240. })
  241. //Execd (Execute & detach) run another script and detach the execution
  242. vm.Set("execd", func(call otto.FunctionCall) otto.Value {
  243. //Check if the pkg is already registered
  244. scriptName, err := call.Argument(0).ToString()
  245. if err != nil {
  246. g.RaiseError(err)
  247. return otto.FalseValue()
  248. }
  249. //Carry the payload to the forked process if there are any
  250. payload, _ := call.Argument(1).ToString()
  251. //Check if the script file exists
  252. targetScriptPath := arozfs.ToSlash(filepath.Join(filepath.Dir(scriptPath), scriptName))
  253. if fsh != nil {
  254. if !fsh.FileSystemAbstraction.FileExists(targetScriptPath) {
  255. g.RaiseError(errors.New("[AGI] Target path not exists!"))
  256. return otto.FalseValue()
  257. }
  258. } else {
  259. if !filesystem.FileExists(targetScriptPath) {
  260. g.RaiseError(errors.New("[AGI] Target path not exists!"))
  261. return otto.FalseValue()
  262. }
  263. }
  264. //Run the script
  265. scriptContent, _ := os.ReadFile(targetScriptPath)
  266. go func() {
  267. //Create a new VM to execute the script (also for isolation)
  268. vm := otto.New()
  269. //Inject standard libs into the vm
  270. g.injectStandardLibs(vm, scriptPath, scriptScope)
  271. //Release any library resources this detached VM opens when it finishes
  272. defer g.injectUserFunctions(vm, fsh, scriptPath, scriptScope, u, w, r)()
  273. vm.Set("PARENT_DETACHED", true)
  274. vm.Set("PARENT_PAYLOAD", payload)
  275. _, err = vm.Run(string(scriptContent))
  276. if err != nil {
  277. //Script execution failed
  278. logger.PrintAndLog("Agi", fmt.Sprint("Script Execution Failed: ", err.Error()), nil)
  279. g.RaiseError(err)
  280. }
  281. }()
  282. return otto.TrueValue()
  283. })
  284. return runCleanups
  285. }