Exemple #1
0
        //--- Methods ---
        public virtual ServiceBE Copy()
        {
            ServiceBE s = new ServiceBE();

            s._ServiceEnabled = _ServiceEnabled;
            s._ServiceLocal   = _ServiceLocal;
            s.Description     = Description;
            s.Id = Id;
            s.ServiceLastEdit   = ServiceLastEdit;
            s.ServiceLastStatus = ServiceLastStatus;
            s.SID         = SID;
            s.Type        = Type;
            s.Uri         = Uri;
            s.Config      = new NameValueCollection(Config);
            s.Preferences = new NameValueCollection(Preferences);
            return(s);
        }
        public static GroupBE BuildGroupFromAuthService(ServiceBE serviceInfo, GroupBE knownGroup, string groupNameToBuild, string authusername, string password) {
            if (serviceInfo == null || string.IsNullOrEmpty(groupNameToBuild))
                return null;

            GroupBE ret = null;
            string errMsg = string.Format(DekiResources.GROUP_DETAILS_LOOKUP_FAILED, groupNameToBuild);
            DreamMessage response = null;
            if (serviceInfo.Uri == null)
                throw new DreamAbortException(DreamMessage.InternalError(string.Format(DekiResources.SERVICE_NOT_STARTED, serviceInfo.Type.ToString(), serviceInfo.SID)));
            
            try {
                Plug dekiExternalAuthPlug = Plug.New(serviceInfo.Uri).At(GROUP_INFO).At(XUri.Encode(groupNameToBuild));

                //Always include credentials with the request if they're supplied
                if (!string.IsNullOrEmpty(authusername)) {
                    dekiExternalAuthPlug = dekiExternalAuthPlug.WithCredentials(authusername, password ?? string.Empty);
                }

                response = dekiExternalAuthPlug.GetAsync().Wait();
            }
            catch (Exception x) {
                throw new DreamResponseException(DreamMessage.InternalError(x), errMsg);
            }

            if (response.IsSuccessful) {
                XDoc groupXml = response.ToDocument();
                if(groupXml.HasName("group") && StringUtil.EqualsInvariant(groupXml["@name"].Contents, groupNameToBuild)) {
                    if (knownGroup == null)
                        ret = new GroupBE();
                    else
                        ret = knownGroup;

                    ret.Name = string.IsNullOrEmpty(ret.Name) ? groupNameToBuild : ret.Name;
                    ret.ServiceId = serviceInfo.Id;
                }

                //TODO (MaxM): Consider looking up existing wiki users and associating them here.
            }
            else {
                switch (response.Status) {
                    case DreamStatus.Unauthorized:
                        throw new DreamAbortException(DreamMessage.AccessDenied(DekiWikiService.AUTHREALM, string.Format(DekiResources.AUTHENTICATION_FAILED_FOR, serviceInfo.Description)));
                    case DreamStatus.InternalError:
                    case DreamStatus.Forbidden:
                    default:
                        throw new DreamAbortException(response, string.Format(DekiResources.GROUP_DETAILS_LOOKUP_FAILED, groupNameToBuild));
                }
            }

            return ret;
        }
        public static XDoc GetServiceXmlVerbose(DekiInstance instance, ServiceBE service, string relation, bool privateDetails) {
            XDoc serviceXml = GetServiceXml(service, relation);

            serviceXml.Start("sid").Value(service.SID ?? string.Empty).End();

            serviceXml.Start("uri").Value(XUri.TryParse(service.Uri)).End();

            serviceXml.Start("type").Value(service.Type.ToString().ToLowerInvariant()).End();
            serviceXml.Start("description").Value(service.Description ?? "").End();

            serviceXml.Elem("date.modified", service.ServiceLastEdit);
            serviceXml.Elem("status", service.ServiceEnabled ? "enabled" : "disabled");

            serviceXml.Start("local").Value(service.ServiceLocal).Attr("deprecated", true).End();
            serviceXml.Elem("init", service.ServiceLocal ? "native" : "remote");
            var serviceInfo = instance.RunningServices[service.Id];
            if(serviceInfo != null && !string.IsNullOrEmpty(serviceInfo.Namespace)) {
                serviceXml.Elem("namespace", serviceInfo.Namespace);
            }
            if(privateDetails) {
                serviceXml.Elem("lasterror", service.ServiceLastStatus ?? "");

                serviceXml.Start("config");

                foreach(string key in service.Config.AllKeys)
                    serviceXml.Start("value").Attr("key", key).Value(service.Config[key]).End();
                serviceXml.End();

                serviceXml.Start("preferences");
                foreach(string key in service.Preferences.AllKeys)
                    serviceXml.Start("value").Attr("key", key).Value(service.Preferences[key]).End();
                serviceXml.End();
            }
            return serviceXml;
        }
 public static XDoc GetServiceXml(ServiceBE service, string relation) {
     XDoc serviceXml = new XDoc(string.IsNullOrEmpty(relation) ? "service" : "service." + relation);
     serviceXml.Attr("id", service.Id);
     serviceXml.Attr("href", DekiContext.Current.ApiUri.At("site", "services", service.Id.ToString()));
     return serviceXml;
 }
        private static ServiceBE NewServiceFromXml(XDoc serviceDoc, string SID, string description, bool? statusEnabled,
                                            bool? localInit, ServiceType? type, string uri, NameValueCollection config, NameValueCollection preferences) {

            ServiceBE service = new ServiceBE();

            if(string.IsNullOrEmpty(SID) && (localInit ?? true)) {
                throw new DreamBadRequestException(DekiResources.SERVICE_CREATE_SID_MISSING);
            }

            if(type == null || type == ServiceType.UNDEFINED) {
                throw new DreamBadRequestException(DekiResources.SERVICE_CREATE_TYPE_MISSING);
            }

            service.SID = SID ?? string.Empty;
            service.Description = description ?? string.Empty;
            service.ServiceEnabled = statusEnabled ?? true;
            service.ServiceLocal = localInit ?? true;
            service.Type = type.Value;
            service.Uri = uri ?? string.Empty;
            service.Preferences = preferences ?? new NameValueCollection();
            service.Config = config ?? new NameValueCollection();
            service.ServiceLastEdit = DateTime.UtcNow;

            return service;
        }
        public static ServiceBE PostServiceFromXml(XDoc serviceDoc, ServiceBE serviceToProcess) {
            uint? serviceId;
            string SID, description, uri;
            bool? statusEnabled;
            bool? localInit;
            ServiceType? type;
            NameValueCollection config, preferences;

            ParseServiceXml(serviceDoc, out serviceId, out SID, out description, out statusEnabled, out localInit, out type, out uri, out config, out preferences);

            //new service
            if(serviceToProcess == null && (serviceId == null || serviceId == 0)) {

                //convert XML input to a service object
                serviceToProcess = NewServiceFromXml(serviceDoc, SID, description, statusEnabled, localInit, type, uri, config, preferences);

                //insert the service
                serviceId = DbUtils.CurrentSession.Services_Insert(serviceToProcess);

                // reload the service
                serviceToProcess = DbUtils.CurrentSession.Services_GetById(serviceId.Value);

            } else {

                //Validate logic of given xml
                if(ServiceBL.IsLocalAuthService(serviceToProcess)) {
                    throw new DreamBadRequestException("Cannot modify built in auth service");
                }

                if(((uri ?? string.Empty) != string.Empty) && (localInit ?? false)) {
                    throw new DreamBadRequestException("Cannot set a URI for a locally initialized service");
                }

                //Stop the service before making any changes
                serviceToProcess = StopService(serviceToProcess);

                //convert XML input to a service object. 
                serviceToProcess = UpdateServiceFromParsedXml(serviceToProcess, SID, description, statusEnabled, localInit, type, uri, config, preferences);

                //Update existing service
                serviceToProcess = UpdateService(serviceToProcess);
            }

            return serviceToProcess;
        }
        public static ServiceBE UpdateService(ServiceBE service) {
            DbUtils.CurrentSession.Services_Delete(service.Id);
            uint serviceId = DbUtils.CurrentSession.Services_Insert(service);

            // reload the service
            return DbUtils.CurrentSession.Services_GetById(serviceId);
        }
        private static ServiceBE StopService(uint serviceId, ServiceBE service, ServiceStopType stopType) {
            DekiContext context = DekiContext.Current;
            bool saveBe = false;
            if(service != null && service.ServiceLocal) {

                // local services should have their uri cleared out
                service.Uri = null;
                saveBe = true;
            }
            if(service != null && stopType == ServiceStopType.Disable) {

                // wipe out last error on disable
                service.ServiceLastStatus = null;
                saveBe = true;
            }
            var serviceInfo = context.Instance.RunningServices[serviceId];

            if(serviceInfo == null) {
                if(saveBe) {
                    service = UpdateService(service) ?? service;
                }

                // service is not registered as running, we're done here
                return service;
            }

            try {
                context.Instance.DeregisterService(serviceId);
            } catch(Exception e) {

                // log the error, but ignore it otherwise
                if(service == null) {
                    context.Instance.Log.WarnExceptionMethodCall(e, "StopService", string.Format("Unable to stop {0} service id '{1}'", serviceInfo.IsLocal ? "local" : "remote", serviceId));
                } else {
                    context.Instance.Log.WarnExceptionMethodCall(e, "StopService", string.Format("Unable to stop {0} service id '{1}' with SID '{2}'", serviceInfo.IsLocal ? "local" : "remote", serviceId, service.SID));
                }
            }

            if(service != null && (stopType == ServiceStopType.Disable || saveBe)) {
                service.ServiceEnabled = false;
                service = UpdateService(service) ?? service;
            }
            return service;
        }
Exemple #9
0
        private static void ParseUserXml(XDoc userDoc, out uint? id, out string username, out string email, out string fullname, out ServiceBE authService, out RoleBE role, out bool? active, out string language, out string timezone) {

            username = userDoc["username"].AsText;
            email = userDoc["email"].AsText;
            fullname = userDoc["fullname"].AsText;
            language = userDoc["language"].AsText;
            timezone = userDoc["timezone"].AsText;
            string authserviceidstr = userDoc["service.authentication/@id"].AsText;
            string rolestr = userDoc["permissions.user/role"].AsText;
            string statusStr = userDoc["status"].AsText;
            authService = null;
            role = null;

            id = null;

            if(!userDoc["@id"].IsEmpty) {
                uint id_temp;
                if(!uint.TryParse(userDoc["@id"].Contents, out id_temp)) {
                    throw new UserIdAttrInvalidArgumentException();
                }
                id = id_temp;
            }

            if(!string.IsNullOrEmpty(authserviceidstr)) {
                uint serviceid;
                if(!uint.TryParse(authserviceidstr, out serviceid))
                    throw new ServiceAuthIdAttrInvalidArgumentException();

                authService = ServiceBL.GetServiceById(serviceid);
                if(authService == null)
                    throw new ServiceDoesNotExistInvalidArgumentException(serviceid);
            }

            if(!string.IsNullOrEmpty(rolestr)) {
                role = PermissionsBL.GetRoleByName(rolestr);
                if(role == null)
                    throw new RoleDoesNotExistInvalidArgumentException(rolestr);
            }

            if(!string.IsNullOrEmpty(statusStr)) {
                switch(statusStr.ToLowerInvariant()) {
                case "active":
                    active = true;
                    break;
                case "inactive":
                    active = false;
                    break;
                default:
                    throw new UserStatusAttrInvalidArgumentException();
                }
            } else {
                active = null;
            }

            if(!string.IsNullOrEmpty(timezone)) {
                if(!timeZoneRegex.Match(timezone).Success) {
                    throw new UserTimezoneInvalidArgumentException();
                }
            }

            if(!string.IsNullOrEmpty(language)) {
                string[] validLanguages = DekiContext.Current.Instance.Languages.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                string tempLanguage = language;
                if(!Array.Exists(validLanguages, delegate(string temp) { return temp.EqualsInvariantIgnoreCase(tempLanguage); })) {
                    throw new UserInvalidLanguageException();
                }
            }
        }
Exemple #10
0
        private static UserBE UpdateUserFromXml(UserBE userToUpdate, XDoc userDoc, string username, string email, string fullname, ServiceBE authservice, RoleBE role, bool? active, string externalusername, string externalpassword, string language, string timezone, out List<GroupBE> externalGroups) {
            externalGroups = null;
            if(userToUpdate.Name != username && !string.IsNullOrEmpty(username)) {
                if(UserBL.IsAnonymous(userToUpdate)) {
                    throw new UserAnonymousEditInvalidOperationException();
                }
                userToUpdate = RenameUser(userToUpdate, username, fullname ?? userToUpdate.RealName);
            }

            //Modify a user's authentication service
            if(authservice != null && authservice.Id != userToUpdate.ServiceId) {
                if(UserBL.IsAnonymous(userToUpdate)) {
                    throw new UserAnonymousEditInvalidOperationException();
                }

                if(ServiceBL.IsLocalAuthService(authservice)) {

                    //external to local
                    userToUpdate.ExternalName = null;
                    userToUpdate.ServiceId = authservice.Id;

                } else {

                    //(local or external) to external
                    userToUpdate = ExternalServiceSA.BuildUserFromAuthService(authservice, userToUpdate, userToUpdate.Name, true, externalusername, externalpassword, out externalGroups);
                    if(userToUpdate == null) {
                        throw new UserAuthChangeFatalException();
                    }

                    //Does the external account already exist?
                    UserBE matchingExternalAccount = DbUtils.CurrentSession.Users_GetByExternalName(userToUpdate.ExternalName, userToUpdate.ServiceId);
                    if(matchingExternalAccount != null) {
                        throw new ExternalUserExistsConflictException(matchingExternalAccount.Name, matchingExternalAccount.ExternalName, matchingExternalAccount.ServiceId);
                    }
                }
            }

            if(email != null) {
                if(UserBL.IsAnonymous(userToUpdate) && email != userToUpdate.Email) {
                    throw new UserAnonymousEditInvalidOperationException();
                }

                userToUpdate.Email = email;
            }

            if(!string.IsNullOrEmpty(fullname))
                userToUpdate.RealName = fullname;

            if(active != null) {
                
                // disabling user
                if(userToUpdate.UserActive && !active.Value) {

                    // cannot disable anonymous user
                    if(UserBL.IsAnonymous(userToUpdate)) {
                        throw new UserAnonymousDeactivationInvalidOperationException();
                    }

                    // cannot disable owner
                    if(DekiContext.Current.LicenseManager.GetSiteOwnerUserId().GetValueOrDefault(0) == userToUpdate.ID) {
                        throw new UserOwnerDeactivationConflict();
                    }
                }

                //throw exception if licensing does not allow activating a user
                if(!userToUpdate.UserActive && active.Value) {
                    DekiContext.Current.LicenseManager.IsUserCreationAllowed(true);
                }

                userToUpdate.UserActive = active.Value;
            }

            if(role != null && role.ID != userToUpdate.RoleId) {
                PermissionsBL.CheckUserAllowed(DekiContext.Current.User, Permissions.ADMIN);
                userToUpdate.RoleId = role.ID;
            }

            if(language != null) {
                userToUpdate.Language = language;
            }

            if(timezone != null) {
                userToUpdate.Timezone = timezone;
            }

            return userToUpdate;
        }
 public ServiceRepository.IServiceInfo CreateLocalService(ServiceBE service, string servicePath, XDoc config) {
     var deki = DekiContext.Current.Deki;
     Plug location = deki.InternalCreateService(servicePath, service.SID, config, new Result<Plug>()).Wait();
     XUri sid = XUri.TryParse(service.SID);
     string license;
     DateTime? expiration;
     if(deki.TryGetServiceLicense(sid, out license, out expiration) && (expiration != null)) {
         lock(_serviceExpirations) {
             TaskTimer timer;
             if(_serviceExpirations.TryGetValue(service.Id, out timer)) {
                 timer.Cancel();
             }
             _serviceExpirations[service.Id] = TimerFactory.New(expiration.Value, _ => ServiceBL.StopService(service), null, TaskEnv.Clone());
         }
     }
     return RunningServices.RegisterService(service, location.Uri, true);
 }
 //--- Methods ---
 public ServiceRepository.IServiceInfo RegisterRemoteService(ServiceBE service, XUri serviceUri) {
     return RunningServices.RegisterService(service, serviceUri, false);
 }
        private static void ParseGroupXml(XDoc groupDoc, out uint? id, out string name, out ServiceBE authService, out RoleBE role, out UserBE[] userList) {

            name = groupDoc["groupname"].AsText ?? groupDoc["name"].AsText;
            string authserviceidstr = groupDoc["service.authentication/@id"].AsText;
            string rolestr = groupDoc["permissions.group/role"].AsText;
            authService = null;
            role = null;
            id = null;


            if (!groupDoc["@id"].IsEmpty) {
                uint id_temp;
                if (!uint.TryParse(groupDoc["@id"].Contents, out id_temp))
                    throw new DreamBadRequestException(DekiResources.GROUP_ID_ATTR_INVALID);
                id = id_temp;
            }

            if (!string.IsNullOrEmpty(authserviceidstr)) {
                uint serviceid;
                if (!uint.TryParse(authserviceidstr, out serviceid))
                    throw new DreamBadRequestException(DekiResources.SERVICE_AUTH_ID_ATTR_INVALID);

                authService = ServiceBL.GetServiceById(serviceid);
                if (authService == null)
                    throw new DreamBadRequestException(string.Format(DekiResources.SERVICE_DOES_NOT_EXIST, serviceid));
            }

            if (!string.IsNullOrEmpty(rolestr)) {
                role = PermissionsBL.GetRoleByName(rolestr);
                if (role == null)
                    throw new DreamBadRequestException(string.Format(DekiResources.ROLE_DOES_NOT_EXIST, rolestr));
            } else {
                role = PermissionsBL.RetrieveDefaultRoleForNewAccounts();
            }
            if (!groupDoc["users"].IsEmpty) {
                userList = ReadUserListXml(groupDoc["users"]);
            } else
                userList = new UserBE[] { };
        }
        private static void ValidateGroupMemberList(ServiceBE groupService, UserBE[] potentialMembers) {

            //Groups belonging to built-in auth service are allowed to contain users from remote services
            if (!ServiceBL.IsLocalAuthService(groupService)) {
                foreach (UserBE u in potentialMembers) {
                    if (u.ServiceId != groupService.Id)
                        throw new DreamBadRequestException(DekiResources.GROUP_MEMBERS_REQUIRE_SAME_AUTH);
                }
            }
        }
Exemple #15
0
        private IList<ServiceBE> Services_Populate(IDataReader dr) {

            // read all services
            List<ServiceBE> orderedServiceList = new List<ServiceBE>();
            Dictionary<uint, ServiceBE> result = new Dictionary<uint, ServiceBE>();
            ServiceBE s = null;
            while(dr.Read()) {
                s = new ServiceBE();
                s._ServiceEnabled = dr.Read<byte>("service_enabled");
                s._ServiceLocal = dr.Read<byte>("service_local");
                s.Description = dr.Read<string>("service_description");
                s.Id = dr.Read<uint>("service_id");
                s.ServiceLastEdit = dr.Read<DateTime>("service_last_edit");
                s.ServiceLastStatus = dr.Read<string>("service_last_status");
                s.SID = dr.Read<string>("service_sid");
                s.Type = dr.Read<ServiceType>("service_type");
                s.Uri = dr.Read<string>("service_uri");

                result.Add(s.Id, s);
                orderedServiceList.Add(s);
            }

            // read config key/value pairs for each service
            dr.NextResult();
            while(dr.Read()) {
                uint serviceId = DbUtils.Convert.To<uint>(dr["service_id"], 0);
                if(serviceId != 0 && result.ContainsKey(serviceId)) {
                    s = result[serviceId];
                    string config_name = DbUtils.Convert.To<string>(dr["config_name"], "");
                    string config_value = DbUtils.Convert.To<string>(dr["config_value"], "");
                    s.Config[config_name] = config_value;
                }
            }

            //  read preference key/value pairs for each service
            dr.NextResult();
            while(dr.Read()) {
                uint serviceId = DbUtils.Convert.To<uint>(dr["service_id"], 0);
                if(serviceId != 0 && result.ContainsKey(serviceId)) {
                    s = result[serviceId];
                    string pref_name = DbUtils.Convert.To<string>(dr["pref_name"], "");
                    string pref_value = DbUtils.Convert.To<string>(dr["pref_value"], "");
                    s.Preferences[pref_name] = pref_value;
                }
            }

            return orderedServiceList;
        }
 public static ServiceBE StopService(ServiceBE service) {
     return StopService(service.Id, service, ServiceStopType.Disable);
 }
Exemple #17
0
        //--- Class Methods ---
        public static void StartExtensionService(DekiContext context, ServiceBE service, ServiceRepository.IServiceInfo serviceInfo, bool forceRefresh) {

            // retrieve document describing the extension functions
            XUri uri = new XUri(service.Uri);
            XDoc manifest = null;
            DekiWikiService deki = context.Deki;
            var extension = serviceInfo.Extension;
            if(!service.ServiceLocal) {
                lock(deki.RemoteExtensionLibraries) {
                    deki.RemoteExtensionLibraries.TryGetValue(uri, out manifest);
                }
            }
            if(manifest == null || forceRefresh) {
                manifest = Plug.New(uri).Get().ToDocument();

                // normalize the extension XML
                manifest = manifest.TransformAsXml(_extensionConverterXslt);

                // check if document describes a valid extension: either the extension has no functions, or the functions have end-points
                if(manifest.HasName("extension") && ((manifest["function"].ListLength == 0) || (manifest["function/uri"].ListLength > 0))) {

                    // add source uri for service
                    manifest.Attr("uri", uri);

                    // register service in extension list
                    lock(deki.RemoteExtensionLibraries) {
                        deki.RemoteExtensionLibraries[uri] = manifest;
                    }
                } else {
                    throw new ExtensionRemoveServiceInvalidOperationException(uri);
                }
            }
            extension.Manifest = manifest;

            // add function prefix if one is defined
            serviceInfo.Extension.SetPreference("namespace.custom", service.Preferences["namespace"]);
            string serviceNamespace = service.Preferences["namespace"] ?? manifest["namespace"].AsText;
            if(serviceNamespace != null) {
                serviceNamespace = serviceNamespace.Trim();
                if(string.IsNullOrEmpty(serviceInfo.Namespace)) {

                    // Note (arnec): Namespace from preferences is assigned at service creation. If we do not have one at this
                    // point, it came from the extension manifest and needs to be registered as our default. Otherwise the
                    // preference override persists as the namespace.
                    context.Instance.RunningServices.RegisterNamespace(serviceInfo, serviceNamespace);
                }
                if(serviceNamespace.Length != 0) {
                    if(!DekiScriptParser.IsIdentifier(serviceNamespace)) {
                        throw new ExtensionNamespaceInvalidArgumentException(service.Preferences["namespace"] ?? manifest["namespace"].AsText);
                    }
                } else {
                    serviceNamespace = null;
                }
            }
            serviceNamespace = (serviceNamespace == null) ? string.Empty : (serviceNamespace + ".");

            // add custom library title

            extension.SetPreference("title.custom", service.Preferences["title"]);
            extension.SetPreference("label.custom", service.Preferences["label"]);
            extension.SetPreference("description.custom", service.Preferences["description"]);
            extension.SetPreference("uri.logo.custom", service.Preferences["uri.logo"]);
            extension.SetPreference("functions", service.Preferences["functions"]);
            extension.SetPreference("protected", service.Preferences["protected"]);

            // add each extension function
            bool.TryParse(service.Preferences["protected"], out extension.IsProtected);
            var functions = new List<ServiceRepository.ExtensionFunctionInfo>();
            foreach(XDoc function in manifest["function"]) {
                XUri functionUri = function["uri"].AsUri;
                if(functionUri != null) {
                    functions.Add(new ServiceRepository.ExtensionFunctionInfo(serviceNamespace + function["name"].Contents, functionUri));
                }
            }
            extension.Functions = functions.ToArray();
        }
        public static bool IsLocalAuthService(ServiceBE service) {
            if(service == null)
                return false;

            return service.Id == BUILT_IN_AUTH_SERVICE_ID;
        }
        public void ConvertConfiguration() {
            Console.Out.Write("Migrating configuration...  ");

            // Delete exisiting custom extensions if they exist
            IList<ServiceBE> services = DbUtils.CurrentSession.Services_GetAll();
            foreach (ServiceBE existingService in services) {
                if (StringUtil.EqualsInvariantIgnoreCase(existingService.Description, "Custom RSS Extension") ||
                    StringUtil.EqualsInvariantIgnoreCase(existingService.Description, "MediaWiki Extension")) {
                    MediaWikiDA.DeleteDWServiceById(existingService.Id);
                }
            }

            // Register the Custom RSS Extension
            ServiceBE rssService = new ServiceBE();
            rssService.Type = ServiceType.EXT;
            rssService.SID = "http://services.mindtouch.com/deki/draft/2007/12/dekiscript";
            rssService.Description = "Custom RSS Extension";
            rssService._ServiceEnabled = 1;
            rssService._ServiceLocal = 1;
            rssService.Config.Add("manifest", "http://scripts.mindtouch.com/ajaxrss.xml");
            MediaWikiDA.InsertDWService(rssService);

            // Register the Media Wiki Extension
            ServiceBE mwService = new ServiceBE();
            mwService.Type = ServiceType.EXT;
            mwService.SID = "http://services.mindtouch.com/deki/draft/2008/04/mediawiki";
            mwService.Description = "MediaWiki Extension";
            mwService._ServiceEnabled = 1;
            mwService._ServiceLocal = 1;

            // populate the config keys used for interwiki links
            Dictionary<Site, NameValueCollection> interWikiBySite = MediaWikiDA.GetInterWikiBySite();
            _MWInterwikiBySite = new Dictionary<Site, string>();
            Dictionary<string, string> interWiki = new Dictionary<string, string>();
            foreach (KeyValuePair<Site, NameValueCollection> interWikiPair in interWikiBySite) {
                foreach (string key in interWikiPair.Value.Keys) {
                    if (_MWInterwikiBySite.ContainsKey(interWikiPair.Key)) {
                        _MWInterwikiBySite[interWikiPair.Key] += "," + key;
                    } else {
                        _MWInterwikiBySite[interWikiPair.Key] = key;
                    }
                    string normalized_key = key.Replace(' ', '_').ToLowerInvariant();
                    string value = null;
                    if (interWiki.TryGetValue(normalized_key, out value)) {
                        if (value != interWikiPair.Value[normalized_key]) {
                            #region logging
                            if (MediaWikiConverterContext.Current.LoggingEnabled) {
                                log["/html/body/table[@id='interWikiConflicts']"].Add(new XDoc("tr").Elem("td", normalized_key).Elem("td", value).Elem("td", interWikiPair.Value[key] + "(" + interWikiPair.Key.Language + ")"));
                            }
                            #endregion
                        }
                    } else {
                        interWiki.Add(normalized_key, interWikiPair.Value[key]);
                        mwService.Config.Set(normalized_key, interWikiPair.Value[key]);
                    }
                }
            }

            // populate the config keys used to map from language to language root
            foreach (Site site in MediaWikiConverterContext.Current.MWSites) {
                mwService.Config.Set("rootpage-" + site.Language, site.DWRootPage);
                mwService.Config.Set("projectname", site.Name);
                mwService.Config.Set("pageseparator", MediaWikiConverterContext.Current.MWPageSeparator.ToString());
            }

            MediaWikiDA.InsertDWService(mwService);

            // configure the wiki languages
            StringBuilder languages = new StringBuilder();
            foreach (Site site in MediaWikiConverterContext.Current.MWSites) {
                if (0 < languages.Length) {
                    languages.Append(",");
                }
                languages.Append(site.Language);
            }
            ConfigBL.SetInstanceSettingsValue("languages", languages.ToString());

            Console.Out.WriteLine("Done!");
        }
        public static void DeleteService(ServiceBE service) {
            if(IsLocalAuthService(service)) {
                throw new DreamBadRequestException("Cannot delete built in authentication service.");
            }

            service = StopService(service);
            DbUtils.CurrentSession.Services_Delete(service.Id);
            DbUtils.CurrentSession.Users_UpdateServicesToLocal(service.Id);
            DbUtils.CurrentSession.Groups_UpdateServicesToLocal(service.Id);
        }
Exemple #21
0
        private static void ValidateGroupMemberList(ServiceBE groupService, UserBE[] potentialMembers) {

            //Groups belonging to built-in auth service are allowed to contain users from remote services
            if(!ServiceBL.IsLocalAuthService(groupService)) {
                foreach(UserBE u in potentialMembers) {
                    if(u.ServiceId != groupService.Id)
                        throw new GroupMembersRequireSameAuthInvalidOperationException();
                }
            }
        }
        private static ServiceBE UpdateServiceFromParsedXml(ServiceBE service, string SID, string description, bool? statusEnabled, bool? localInit,
                                                            ServiceType? type, string uri, NameValueCollection config, NameValueCollection preferences) {

            //Modify only changed (non-null) values

            if(SID != null) {
                service.SID = SID;
            }

            if(service.Description != null) {
                service.Description = description;
            }

            if(statusEnabled != null) {
                service.ServiceEnabled = statusEnabled.Value;
            }

            if(localInit != null) {
                service.ServiceLocal = localInit.Value;
            }

            if(type != null && type.Value != ServiceType.UNDEFINED) {
                service.Type = type.Value;
            }

            if(uri != null) {
                service.Uri = uri;
            }

            if(config != null) {
                service.Config = config;
            }

            if(preferences != null) {
                service.Preferences = preferences;
            }

            return service;
        }
Exemple #23
0
        private static void ParseGroupXml(XDoc groupDoc, out uint? id, out string name, out ServiceBE authService, out RoleBE role, out UserBE[] userList) {

            name = groupDoc["groupname"].AsText ?? groupDoc["name"].AsText;
            string authserviceidstr = groupDoc["service.authentication/@id"].AsText;
            string rolestr = groupDoc["permissions.group/role"].AsText;
            authService = null;
            role = null;
            id = null;


            if(!groupDoc["@id"].IsEmpty) {
                uint id_temp;
                if(!uint.TryParse(groupDoc["@id"].Contents, out id_temp))
                    throw new GroupIdAttributeInvalidArgumentException();
                id = id_temp;
            }

            if(!string.IsNullOrEmpty(authserviceidstr)) {
                uint serviceid;
                if(!uint.TryParse(authserviceidstr, out serviceid))
                    throw new ServiceAuthIdAttrInvalidArgumentException();

                authService = ServiceBL.GetServiceById(serviceid);
                if(authService == null)
                    throw new ServiceDoesNotExistInvalidArgumentException(serviceid);
            }

            if(!string.IsNullOrEmpty(rolestr)) {
                role = PermissionsBL.GetRoleByName(rolestr);
                if(role == null)
                    throw new RoleDoesNotExistInvalidArgumentException(rolestr);
            } else {
                role = PermissionsBL.RetrieveDefaultRoleForNewAccounts();
            }
            if(!groupDoc["users"].IsEmpty) {
                userList = ReadUserListXml(groupDoc["users"]);
            } else
                userList = new UserBE[] { };
        }
        //--- Class Methods ---
        public static ServiceBE StartService(ServiceBE service, bool forceRefresh, bool disableOnFailure) {
           
            // create subordinate request id for service start
            var dreamContext = DreamContext.Current;
            var requestId = dreamContext.GetState<string>(DreamHeaders.DREAM_REQUEST_ID);
            dreamContext.SetState(DreamHeaders.DREAM_REQUEST_ID, requestId + "-service_" + service.Id);

            try {
                Stopwatch stopwatch = new Stopwatch();
                stopwatch.Start();
                service.ServiceLastStatus = string.Empty;
                StopService(service.Id, service, ServiceStopType.Restart);
                DekiContext context = DekiContext.Current;
                bool dirtyServiceEntity = false;
                XUri location;
                ServiceRepository.IServiceInfo serviceInfo = null;
                try {

                    // check if service is local
                    if(service.ServiceLocal) {
                        if(string.IsNullOrEmpty(service.SID)) {
                            throw new Exception("missing SID");
                        }

                        // start service
                        if(IsLocalAuthService(service)) {

                            // this service is the built-in authentication provider; no need to start it
                            location = context.Deki.Self;
                        } else {

                            // convert local service configuration into an xdoc
                            XDoc config = new XDoc("config");
                            foreach(KeyValuePair<string, string> configEntry in ArrayUtil.AllKeyValues(service.Config)) {
                                config.InsertValueAt(configEntry.Key, configEntry.Value);
                            }

                            // if no apikey was provided, create a random one so that CreateService doesn't inject the parent one
                            if(config["apikey"].IsEmpty) {
                                config.Elem("apikey", StringUtil.CreateAlphaNumericKey(16));
                            }

                            // add information for service to callback into deki
                            if(config["uri.deki"].IsEmpty) {
                                config.Elem("uri.deki", context.Deki.Self);
                                config.Elem("wikiid.deki", context.Instance.Id);
                                config.Elem("apikey.deki", context.Deki.MasterApiKey);
                            }

                            // the service location must use the service ID and the instance ID
                            string servicePath = string.Format("services/{0}/{1}", context.Instance.Id, service.Id);
                            serviceInfo = context.Instance.CreateLocalService(service, servicePath, config);
                            location = serviceInfo.ServiceUri;
                        }

                        // check if the service uri has changed since last invocation (happens when service is started for the first time or server GUID has changed)
                        if(!service.Uri.EqualsInvariantIgnoreCase(location.ToString())) {
                            dirtyServiceEntity = true;
                            service.Uri = location.ToString();
                        }
                    } else {
                        if(string.IsNullOrEmpty(service.Uri)) {
                            throw new Exception("missing URI");
                        }
                        location = new XUri(service.Uri);
                        serviceInfo = context.Instance.RegisterRemoteService(service, location);
                    }

                    // check if service is an Extension service
                    if(service.Type == ServiceType.EXT) {
                        ExtensionBL.StartExtensionService(context, service, serviceInfo, forceRefresh);
                    }

                    //Successfully starting a service enables it.
                    if(!service.ServiceEnabled) {
                        dirtyServiceEntity = true;
                        service.ServiceEnabled = true;
                    }
                } catch(Exception e) {
                    dirtyServiceEntity = true;
                    DreamMessage dm = null;
                    if(e is DreamResponseException) {
                        dm = ((DreamResponseException)e).Response;
                        string message = dm.HasDocument ? dm.ToDocument()[".//message"].AsText.IfNullOrEmpty(e.Message) : dm.ToText();
                        service.ServiceLastStatus = string.Format("unable to initialize service ({0})", message);
                    } else {
                        service.ServiceLastStatus = e.GetCoroutineStackTrace();
                    }
                    if(serviceInfo != null) {
                        try {
                            context.Instance.DeregisterService(service.Id);
                        } catch {}
                    }

                    // A service that fails to start becomes disabled if it's started explicitly (not during deki startup)
                    if(disableOnFailure) {
                        service.ServiceEnabled = false;
                    }

                    context.Instance.Log.ErrorExceptionMethodCall(e, "StartService", string.Format("Unable to start local service id '{0}' with SID '{1}' Error: '{2}'", service.Id, service.SID, service.ServiceLastStatus));
                    if(dm != null) {
                        throw new DreamAbortException(dm);
                    } else {
                        throw;
                    }
                } finally {

                    // don't update remote services that haven't changed
                    if(dirtyServiceEntity) {
                        service = UpdateService(service);
                    }
                }
                stopwatch.Stop();
                _log.InfoFormat("Service '{0}' ({1}) started in {2}ms", service.Description, service.SID, stopwatch.ElapsedMilliseconds);
                return service;
            } finally {

                // restore the request id
                dreamContext.SetState(DreamHeaders.DREAM_REQUEST_ID, requestId);
            }
        }
Exemple #25
0
        private static UserBE UpdateUserFromXml(UserBE userToUpdate, XDoc userDoc, string username, string email, string fullname, ServiceBE authservice, RoleBE role, bool? active, string externalusername, string externalpassword, string language, string timezone, out List<GroupBE> externalGroups) {
            externalGroups = null;
            if(userToUpdate.Name != username && !string.IsNullOrEmpty(username)) {
                if(UserBL.IsAnonymous(userToUpdate)) {
                    throw new DreamBadRequestException(DekiResources.ANONYMOUS_USER_EDIT);
                }
                userToUpdate = RenameUser(userToUpdate, username);
            }

            //Modify a user's authentication service
            if(authservice != null && authservice.Id != userToUpdate.ServiceId) {
                if(UserBL.IsAnonymous(userToUpdate)) {
                    throw new DreamBadRequestException(DekiResources.ANONYMOUS_USER_EDIT);
                }

                if(ServiceBL.IsLocalAuthService(authservice)) {

                    //external to local
                    userToUpdate.ExternalName = null;
                    userToUpdate.ServiceId = authservice.Id;

                } else {

                    //(local or external) to external
                    userToUpdate = ExternalServiceSA.BuildUserFromAuthService(authservice, userToUpdate, userToUpdate.Name, true, externalusername, externalpassword, out externalGroups);
                    if(userToUpdate == null) {
                        throw new DreamInternalErrorException(DekiResources.USER_AUTHSERVICE_CHANGE_FAIL);
                    }

                    //Does the external account already exist?
                    UserBE matchingExternalAccount = DbUtils.CurrentSession.Users_GetByExternalName(userToUpdate.ExternalName, userToUpdate.ServiceId);
                    if(matchingExternalAccount != null) {
                        throw new DreamAbortException(DreamMessage.Conflict(string.Format(DekiResources.USER_EXISTS_WITH_EXTERNAL_NAME, matchingExternalAccount.Name, matchingExternalAccount.ExternalName, matchingExternalAccount.ServiceId)));
                    }
                }
            }

            if(email != null) {
                if(UserBL.IsAnonymous(userToUpdate) && email != userToUpdate.Email) {
                    throw new DreamBadRequestException(DekiResources.ANONYMOUS_USER_EDIT);
                }

                userToUpdate.Email = email;
            }

            if(!string.IsNullOrEmpty(fullname))
                userToUpdate.RealName = fullname;

            if(active != null) {
                if(UserBL.IsAnonymous(userToUpdate) && userToUpdate.UserActive && !active.Value) {
                    throw new DreamBadRequestException(DekiResources.DEACTIVATE_ANONYMOUS_NOT_ALLOWED);
                }

                //throw exception if licensing does not allow activating a user
                if(!userToUpdate.UserActive && active.Value) {
                    LicenseBL.IsUserCreationAllowed(true);
                }
                userToUpdate.UserActive = active.Value;
            }

            if(role != null && role.ID != userToUpdate.RoleId) {
                PermissionsBL.CheckUserAllowed(DekiContext.Current.User, Permissions.ADMIN);
                userToUpdate.RoleId = role.ID;
            }

            if(language != null) {
                userToUpdate.Language = language;
            }

            if(timezone != null) {
                userToUpdate.Timezone = timezone;
            }

            return userToUpdate;
        }
 public static XDoc GetServiceXmlVerbose(DekiInstance instance, ServiceBE service, string relation) {
     return GetServiceXmlVerbose(DekiContext.Current.Instance, service, relation, true);
 }
Exemple #27
0
        private static UserBE ReadUserXml(XDoc userDoc, string username, string email, string fullname, ServiceBE authService, RoleBE role, string language, string timezone) {

            UserBE user = new UserBE();

            if(string.IsNullOrEmpty(username))
                throw new DreamBadRequestException(DekiResources.USERNAME_PARAM_INVALID);

            //TODO (MaxM) Consider validation of fullname, email, username

            //Retrieve default auth service for new user if authservice not given
            if(authService == null) {
                authService = ServiceBL.RetrieveLocalAuthService();
            }

            user.Name = username;

            //Default role will be applied if one is not given
            if(role != null)
                user.RoleId = role.ID;

            user.RealName = fullname ?? string.Empty;
            user.ServiceId = authService.Id;
            user.UserActive = true;
            user.Email = email ?? string.Empty;
            user.Language = language;
            user.Timezone = timezone;

            return user;
        }
 //--- Methods ---
 public virtual ServiceBE Copy() {
     ServiceBE s = new ServiceBE();
     s._ServiceEnabled = _ServiceEnabled;
     s._ServiceLocal = _ServiceLocal;
     s.Description = Description;
     s.Id = Id;
     s.ServiceLastEdit = ServiceLastEdit;
     s.ServiceLastStatus = ServiceLastStatus;
     s.SID = SID;
     s.Type = Type;
     s.Uri = Uri;
     s.Config = new NameValueCollection(Config);
     s.Preferences = new NameValueCollection(Preferences);
     return s;
 }
Exemple #29
0
        private static void ParseUserXml(XDoc userDoc, out uint? id, out string username, out string email, out string fullname, out ServiceBE authService, out RoleBE role, out bool? active, out string language, out string timezone) {

            username = userDoc["username"].AsText;
            email = userDoc["email"].AsText;
            fullname = userDoc["fullname"].AsText;
            language = userDoc["language"].AsText;
            timezone = userDoc["timezone"].AsText;
            string authserviceidstr = userDoc["service.authentication/@id"].AsText;
            string rolestr = userDoc["permissions.user/role"].AsText;
            string statusStr = userDoc["status"].AsText;
            authService = null;
            role = null;

            id = null;

            if(!userDoc["@id"].IsEmpty) {
                uint id_temp;
                if(!uint.TryParse(userDoc["@id"].Contents, out id_temp))
                    throw new DreamBadRequestException(DekiResources.USER_ID_ATTR_INVALID);
                id = id_temp;
            }

            if(!string.IsNullOrEmpty(authserviceidstr)) {
                uint serviceid;
                if(!uint.TryParse(authserviceidstr, out serviceid))
                    throw new DreamBadRequestException(DekiResources.SERVICE_AUTH_ID_ATTR_INVALID);

                authService = ServiceBL.GetServiceById(serviceid);
                if(authService == null)
                    throw new DreamBadRequestException(string.Format(DekiResources.SERVICE_DOES_NOT_EXIST, serviceid));
            }

            if(!string.IsNullOrEmpty(rolestr)) {
                role = PermissionsBL.GetRoleByName(rolestr);
                if(role == null)
                    throw new DreamBadRequestException(string.Format(DekiResources.ROLE_DOES_NOT_EXIST, rolestr));
            }

            if(!string.IsNullOrEmpty(statusStr)) {
                switch(statusStr.ToLowerInvariant()) {
                case "active":
                    active = true;
                    break;
                case "inactive":
                    active = false;
                    break;
                default:
                    throw new DreamBadRequestException(DekiResources.USER_STATUS_ATTR_INVALID);
                }
            } else {
                active = null;
            }

            if(!string.IsNullOrEmpty(timezone)) {
                if(!timeZoneRegex.Match(timezone).Success) {
                    throw new DreamBadRequestException(DekiResources.INVALID_TIMEZONE_VALUE);
                }
            }

            if(!string.IsNullOrEmpty(language)) {
                string[] validLanguages = DekiContext.Current.Instance.Languages.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                string tempLanguage = language;
                if(!Array.Exists<string>(validLanguages, delegate(string temp) { return StringUtil.EqualsInvariantIgnoreCase(temp, tempLanguage); })) {
                    throw new DreamBadRequestException(DekiResources.INVALID_LANGUAGE_VALUE);
                }
            }
        }
        public static UserBE BuildUserFromAuthService(ServiceBE serviceInfo, UserBE knownUser, string usernameToBuild, bool bypassAuthentication, string authusername, string password, out List<GroupBE> externalGroups) {
            externalGroups = null;
            if (serviceInfo == null || string.IsNullOrEmpty(usernameToBuild))
                return null;

            //Dont perform external lookup for disabled users
            if (knownUser != null && !knownUser.UserActive)
                return knownUser;

            string errMsg = string.Format(DekiResources.UNABLE_TO_AUTH_WITH_SERVICE, serviceInfo.Type.ToString(), serviceInfo.SID, serviceInfo.Uri);

            if(knownUser != null && !string.IsNullOrEmpty(knownUser.ExternalName)) {
                usernameToBuild = knownUser.ExternalName;
            }

            UserBE ret = null;
            DreamMessage response = null;
            if (serviceInfo.Uri == null)
                throw new DreamAbortException(DreamMessage.InternalError(string.Format(DekiResources.SERVICE_NOT_STARTED, serviceInfo.Type.ToString(), serviceInfo.SID)));
            try {
                Plug dekiExternalAuthPlug;

                //bypassAuthentication is used when you only need user details but not to necessarily authenticate
                if (bypassAuthentication) {

                    //An external auth service's GET: user/{username} does not necessarily require authentication to lookup users but it may. It's up to the service
                    //to decide if anon requests are allowed.
                    dekiExternalAuthPlug = Plug.New(serviceInfo.Uri).At(USER_INFO).At(XUri.Encode(usernameToBuild));
                } else {

                    //Credentials are always needed for GET: authenticate. The user details of the auth'd user is returned with same format as GET: user/{username}
                    dekiExternalAuthPlug = Plug.New(serviceInfo.Uri).At(AUTHENTICATE_PATH);
                }

                //Always include credentials with the request if they're supplied
                if (!string.IsNullOrEmpty(authusername)) {
                    dekiExternalAuthPlug = dekiExternalAuthPlug.WithCredentials(authusername, password ?? string.Empty);
                }

                response = dekiExternalAuthPlug.GetAsync().Wait();
            }
            catch (Exception x) {
                throw new DreamResponseException(DreamMessage.InternalError(x), errMsg);
            }

            if (response.IsSuccessful) {
                XDoc userXml = response.ToDocument();

                if (userXml == null || userXml.IsEmpty) {
                    throw new DreamInternalErrorException(string.Format("Empty or not well-formed XML returned from remote auth service: " + userXml.ToPrettyString()));
                }

                string nameFromAuthProvider = userXml["@name"].Contents;
                if (!StringUtil.EqualsInvariantIgnoreCase(nameFromAuthProvider, usernameToBuild)) {
                    throw new DreamInternalErrorException(string.Format(DekiResources.UNEXPECTED_EXTERNAL_USERNAME, userXml["@name"].AsText, usernameToBuild));
                }

                if (knownUser != null)
                    ret = knownUser;
                else
                    ret = new UserBE();

                ret.Email = string.IsNullOrEmpty(userXml["email"].AsText) ? (ret.Email ?? string.Empty) : userXml["email"].AsText;
                
                //Build the realname (exposed as 'fullname' in user xml) by saving it as '{firstname} {lastname}'
                string externalFirstName = userXml["firstname"].AsText ?? string.Empty;
                string externalLastName = userXml["lastname"].AsText ?? string.Empty;
                string separator = externalLastName.Length > 0 && externalFirstName.Length > 0 ? ", " : string.Empty;
                
                // NOTE (maxm): Fullname sync is disabled for now. Refer to bug 7855
#if !DISABLE_REAL_NAME_SYNCHRONIZATION
                ret.RealName = string.Format("{0}{1}{2}", externalLastName, separator, externalFirstName);
#endif

                ret.ServiceId = serviceInfo.Id;
                ret.Touched = DateTime.UtcNow;

                ret.ExternalName = string.IsNullOrEmpty(ret.ExternalName) ? nameFromAuthProvider : ret.ExternalName;
                ret.Name = string.IsNullOrEmpty(ret.Name) ? nameFromAuthProvider : ret.Name;

                //For new users, the name must be normalized and unique
                if (ret.ID == 0) {

                    string nameFromExternalName = string.Empty;

                    //Allow using a displayname from an external provider only for new accounts
                    if (!userXml["@displayname"].IsEmpty) {
                        nameFromExternalName = userXml["@displayname"].AsText;   
                    }
                    else {
                        nameFromExternalName = ret.ExternalName;
                    }

                    ret.Name = UserBL.NormalizeExternalNameToWikiUsername(nameFromExternalName);    
                }

                //Build group objects out of the user's group membership list
                externalGroups = new List<GroupBE>();
                IList<GroupBE> userGroups = DbUtils.CurrentSession.Groups_GetByUser(ret.ID);

                //Preserve local groups for existing users
                if (ret.ID != 0 && userGroups != null) {
                    foreach (GroupBE g in userGroups) {
                        if (ServiceBL.IsLocalAuthService(g.ServiceId)) {
                            externalGroups.Add(g);
                        }
                    }
                }

                foreach (XDoc group in userXml["groups/group"]) {
                    GroupBE g = new GroupBE();
                    g.Name = group["@name"].AsText;
                    g.ServiceId = serviceInfo.Id;
                    if (!string.IsNullOrEmpty(g.Name))
                        externalGroups.Add(g);
                }
            }
            else {
                switch (response.Status) {
                    case DreamStatus.Unauthorized:
                        if (bypassAuthentication) {
                            DekiContext.Current.Instance.Log.Warn(string.Format("Attempted to lookup user info on auth provider '{0}' but failed since it required credentials", serviceInfo.Id));
                        }

                        throw new DreamAbortException(DreamMessage.AccessDenied(DekiWikiService.AUTHREALM, string.Format(DekiResources.AUTHENTICATION_FAILED_FOR, serviceInfo.Description)));
                    case DreamStatus.InternalError:

                    case DreamStatus.Forbidden:
                    default:
                        throw new DreamAbortException(response, errMsg);
                }
            }
            
            return ret;
        }
Exemple #31
0
        public uint Services_Insert(ServiceBE service) {

            StringBuilder query = null;
            if(service.Id == 0) {

                //new service
                query = new StringBuilder(@" /* Services_Insert */
insert into services (service_type, service_sid, service_uri, service_description, service_local, service_enabled, service_last_edit, service_last_status)
values (?TYPE, ?SID, ?URI, ?DESC, ?LOCAL, ?ENABLED, ?TIMESTAMP, ?LASTSTATUS);
");
                query.AppendLine("select LAST_INSERT_ID() into @service_id;");
                query.AppendLine("select LAST_INSERT_ID() as service_id;");
            } else {

                //update existing service
                query = new StringBuilder(@" /* Services_Insert (with id) */
insert into services (service_id, service_type, service_sid, service_uri, service_description, service_local, service_enabled, service_last_edit, service_last_status)
values (?ID, ?TYPE, ?SID, ?URI, ?DESC, ?LOCAL, ?ENABLED, ?TIMESTAMP, ?LASTSTATUS);
");
                query.AppendLine(string.Format("select {0} into @service_id;", service.Id));
                query.AppendLine(string.Format("select {0} as service_id;", service.Id));
            }

            if(service.Preferences != null && service.Preferences.Count > 0) {
                query.Append("insert into service_prefs (service_id, pref_name, pref_value) values ");
                for(int i = 0; i < service.Preferences.AllKeys.Length; i++) {
                    string key = DataCommand.MakeSqlSafe(service.Preferences.AllKeys[i]);
                    string val = DataCommand.MakeSqlSafe(service.Preferences[key]);
                    query.AppendFormat("{0}(@service_id, '{1}', '{2}')\n", i > 0 ? "," : string.Empty, key, val);
                }
                query.AppendLine(";");
            }

            if(service.Config != null && service.Config.Count > 0) {
                query.Append("insert into service_config (service_id, config_name, config_value) values ");
                for(int i = 0; i < service.Config.AllKeys.Length; i++) {
                    string key = DataCommand.MakeSqlSafe(service.Config.AllKeys[i]);
                    string val = DataCommand.MakeSqlSafe(service.Config[key]);
                    query.AppendFormat("{0}(@service_id, '{1}', '{2}')\n", i > 0 ? "," : string.Empty, key, val);
                }
                query.AppendLine(";");
            }
            uint serviceId = 0;
            try {
                serviceId = Catalog.NewQuery(query.ToString())
                    .With("ID", service.Id)
                    .With("TYPE", service.Type.ToString())
                    .With("SID", service.SID)
                    .With("URI", service.Uri)
                    .With("DESC", service.Description)
                    .With("LOCAL", service.ServiceLocal)
                    .With("ENABLED", service.ServiceEnabled)
                    .With("TIMESTAMP", service.ServiceLastEdit)
                    .With("LASTSTATUS", service.ServiceLastStatus)
                    .ReadAsUInt() ?? 0;
            } catch(MySqlException e) {

                // catch Duplicate Key (1062)
                if(e.Number == 1062) {
                    serviceId = service.Id;
                } else {
                    throw;
                }
            }
            return serviceId;
        }