Ejemplo n.º 1
0
        public ILoginProcess GetActiveProcess(IEntitySession session, LoginProcessType processType, string token)
        {
            Util.Check(!string.IsNullOrWhiteSpace(token), "Process token may not be null");
            var context = session.Context;
            //get process without expiration checking, we check it later
            var query = from p in session.EntitySet <ILoginProcess>()
                        where p.ProcessType == processType && p.Token == token
                        select p;
            var process = query.FirstOrDefault();

            context.ThrowIfNull(process, ClientFaultCodes.ObjectNotFound, "Process", "Login process not found.");
            if (process.Status != LoginProcessStatus.Active)
            {
                return(null);
            }
            if (process.FailCount >= _settings.MaxFailCount)
            {
                process.Status = LoginProcessStatus.AbortedAsFraud;
                OnLoginEvent(context, LoginEventType.ProcessAborted, process.Login, "Aborted - too many failures.");
                session.SaveChanges();
                return(null);
            }
            var userName = process.Login.UserName;

            if (process.ExpiresOn < App.TimeService.UtcNow)
            {
                process.Status = LoginProcessStatus.Expired;
                OnLoginEvent(context, LoginEventType.ProcessAborted, process.Login, "Expired.");
                session.SaveChanges();
                return(null);
            }
            return(process);
        }
Ejemplo n.º 2
0
 public void AddBookToOrder(IEntitySession session, Guid orderId, Guid bookId, int quantity)
 {
     var order = session.GetEntity<IBookOrder>(orderId, LockOptions.ForUpdate);
       var book = session.GetEntity<IBook>(bookId);
       var orderLine = session.NewEntity<IBookOrderLine>();
       orderLine.Order = order;
       orderLine.Book = book;
       orderLine.Quantity = quantity;
       orderLine.Price = book.Price;
       order.Lines.Add(orderLine);
       order.Total = order.Lines.Sum(ol => ol.Price * ol.Quantity);
       session.SaveChanges();
 }
Ejemplo n.º 3
0
        public void AddBookToOrder(IEntitySession session, Guid orderId, Guid bookId, byte quantity)
        {
            var order     = session.GetEntity <IBookOrder>(orderId, LockOptions.ForUpdate);
            var book      = session.GetEntity <IBook>(bookId);
            var orderLine = session.NewEntity <IBookOrderLine>();

            orderLine.Order    = order;
            orderLine.Book     = book;
            orderLine.Quantity = quantity;
            orderLine.Price    = book.Price;
            order.Lines.Add(orderLine);
            order.Total = order.Lines.Sum(ol => ol.Price * ol.Quantity);
            session.SaveChanges();
        }
Ejemplo n.º 4
0
        }//method

        private void ImportBooksInCategory(BookCategory category, string keyword, int count)
        {
            var skip         = 0;
            var currentCount = 0;

            while (currentCount < count)
            {
                var volumeSet = _client.GetVolumes(keyword, skip);
                skip += volumeSet.Items.Count;
                foreach (var volume in volumeSet.Items)
                {
                    var vinfo = volume.VolumeInfo;
                    if (string.IsNullOrWhiteSpace(vinfo.Publisher)) //some books don't have publisher, just skip these
                    {
                        continue;
                    }
                    var title = Trim(vinfo.Title, 120);
                    if (_bookCache.ContainsKey(title))
                    {
                        continue;
                    }
                    currentCount++;
                    var ipub    = GetCreatePublisher(vinfo.Publisher);
                    var pubDate = ParsePublishedDate(vinfo.PublishedDate);
                    var image   = LoadImageFromUrl(vinfo.ImageLinks.Thumbnail);
                    var price   = GetPrice(volume.SaleInfo);
                    var ibook   = _session.NewBook(BookEdition.Paperback, category, title, vinfo.SubTitle, ipub, pubDate, price, coverImage: image);
                    ibook.Abstract = vinfo.Description;
                    _bookCache.Add(vinfo.Title, ibook);
                    //parse authors
                    if (vinfo.Authors != null)
                    {
                        foreach (var author in vinfo.Authors)
                        {
                            var iauth = GetCreateAuthor(author);
                            if (iauth != null)
                            {
                                ibook.Authors.Add(iauth);
                            }
                        }
                    }
                }//foreach volume
            }
            try {
                _session.SaveChanges();
            } catch (Exception ex) {
                System.Diagnostics.Debug.WriteLine("Exception: " + ex.ToLogString());
                throw;
            }
        }
Ejemplo n.º 5
0
        // Can be called explicitly outside of migrations to unencrypt existing values
        public static void UnencryptFactorValues(IEntitySession session)
        {
            var loginConfig = session.Context.App.GetConfig <LoginModuleSettings>();
            var errLog      = session.Context.App.ErrorLog;
            int batchSize   = 200;
            int skip        = 0;

            try {
                var factors = session.EntitySet <ILoginExtraFactor>()
                              .Where(f => f.FactorValue == null).OrderBy(f => f.CreatedOn)
                              .Skip(skip).Take(batchSize).ToList();
                skip += batchSize;
                if (factors.Count == 0)
                {
                    return;
                }
                //preload all EncryptedValue records
                var encrIds  = factors.Select(f => f.Info_Id).ToList();
                var encrRecs = session.EntitySet <IEncryptedData>().Where(ed => encrIds.Contains(ed.Id));
                foreach (var f in factors)
                {
                    if (f.Info_Id == null || f.Info_Id.Value == Guid.Empty)
                    {
                        continue;
                    }
                    var ed = session.GetEntity <IEncryptedData>(f.Info_Id); //should be preloaded
                    if (ed == null)
                    {
                        continue;
                    }
                    f.FactorValue = ed.DecryptString(loginConfig.EncryptionChannelName);
                    f.Info_Id     = null;
                }
                session.SaveChanges();
            } catch (Exception ex) {
                if (errLog != null)
                {
                    errLog.LogError(ex, session.Context);
                }
                if (!SuppressMigrationErrors)
                {
                    throw;
                }
            }
        }
Ejemplo n.º 6
0
        private void RunRandomReadWriteOp(Guid[] docIds, LockType readLock, LockType writeLock, int readWriteCount)
        {
            var            rand    = new Random(Thread.CurrentThread.ManagedThreadId);
            IEntitySession session = null;
            // Use context with its own buffered op log - all entries related to single load/update operation are buffered,
            // and then flushed together at the end of loop body; so they will appear together in the output file
            var ctx = new OperationContext(_app);

            ctx.Log = new BufferedLog(ctx.LogContext);

            for (int i = 0; i < readWriteCount; i++)
            {
                session = ctx.OpenSession();
                var        randomDocId = docIds[rand.Next(docIds.Length)];
                IDoc       iDoc;
                IDocDetail iDet;
                int        randomOp = -1;
                try {
                    Thread.Yield();
                    var randomValueName = "N" + rand.Next(5);
                    randomOp = rand.Next(7);
                    switch (randomOp)
                    {
                    case 0:
                    case 1: //read and check total
                        session.LogMessage("\r\n----------------- Load, check Doc total ---------------------");
                        iDoc = session.GetEntity <IDoc>(randomDocId, readLock);
                        Thread.Yield(); //to let other thread mess it up
                        var valuesSum = iDoc.Details.Sum(v => v.Value);
                        if (valuesSum != iDoc.Total)
                        {
                            session.LogMessage("!!!! Sum error: Doc.Total: {0}, Sum(Value): {1}", iDoc.Total, valuesSum);
                            Interlocked.Increment(ref _sumErrorCount);
                        }
                        Thread.Yield();
                        session.ReleaseLocks();
                        session.LogMessage("\r\n-------------- Completed Load/check total ---------------------");
                        break;

                    case 2:
                    case 3:
                    case 4:
                        //insert/update, we give it an edge over deletes, to have 2 upserts for 1 delete
                        session.LogMessage("\r\n----------------- Update/insert DocDetail ---------------------");
                        iDoc = session.GetEntity <IDoc>(randomDocId, writeLock);
                        Thread.Yield();
                        iDet = iDoc.Details.FirstOrDefault(iv => iv.Name == randomValueName);
                        if (iDet == null)
                        {
                            iDet      = session.NewEntity <IDocDetail>();
                            iDet.Doc  = iDoc;
                            iDet.Name = randomValueName;
                            iDoc.Details.Add(iDet); //to correctly calculate total
                        }
                        iDet.Value = rand.Next(10);
                        iDoc.Total = iDoc.Details.Sum(v => v.Value);
                        Thread.Yield();
                        session.SaveChanges();
                        session.LogMessage("\r\n------Completed Update/insert doc detail ---------------------");

                        var entSession = (Vita.Entities.Runtime.EntitySession)session;
                        if (entSession.CurrentConnection != null)
                        {
                            Debugger.Break(); //check that connection is closed and removed from session
                        }
                        break;

                    case 6: //delete if exists
                        session.LogMessage("\r\n----------------- Delete doc detail ---------------------");
                        //Note: deletes do not throw any errors - if record does not exist (had been just deleted), stored proc do not throw error
                        iDoc = session.GetEntity <IDoc>(randomDocId, writeLock);
                        Thread.Yield(); //allow others mess up
                        iDet = iDoc.Details.FirstOrDefault(iv => iv.Name == randomValueName);
                        if (iDet != null)
                        {
                            session.DeleteEntity(iDet);
                            iDoc.Details.Remove(iDet);
                            iDoc.Total = iDoc.Details.Sum(v => v.Value);
                            session.SaveChanges(); // even if there's no changes, it will release lock
                        }
                        else
                        {
                            session.ReleaseLocks();
                        }
                        session.LogMessage("\r\n----------------- Completed delete doc detail ---------------------");
                        break;
                    }//switch
                } catch (Exception ex) { //most will be UniqueIndexViolation
                    Debug.WriteLine(ex.ToLogString());
                    System.Threading.Interlocked.Increment(ref _updateErrorCount);
                    session.Context.Log.AddEntry(new ErrorLogEntry(ctx.LogContext, ex));
                    var entSession = (Vita.Entities.Runtime.EntitySession)session;
                    if (entSession.CurrentConnection != null)
                    {
                        entSession.CurrentConnection.Close();
                    }
                    //session.Context.Log.Flush();
                    _app.Flush();
                } finally {
                    //_app.Flush();
                }
                //session.Context.Log.Flush();
            } //for i
        }     //method
Ejemplo n.º 7
0
        private void RunRandomReadWriteOp(Guid[] docIds, LockOptions readOptions, LockOptions writeOptions, int readWriteCount)
        {
            var            rand    = new Random(Thread.CurrentThread.ManagedThreadId);
            IEntitySession session = null;

            for (int i = 0; i < readWriteCount; i++)
            {
                var        randomDocId     = docIds[rand.Next(docIds.Length)];
                var        randomValueName = "N" + rand.Next(5);
                IDoc       iDoc;
                IDocDetail iDet;
                int        randomOp = -1;
                try {
                    Thread.Yield();
                    session  = _app.OpenSession();
                    randomOp = rand.Next(5);
                    switch (randomOp)
                    {
                    case 0:
                    case 1: //insert/update, we give it an edge over deletes, to have 2 upserts for 1 delete
                        session.LogMessage("\r\n----------------- Update/insert value ---------------------");
                        iDoc = session.GetEntity <IDoc>(randomDocId, writeOptions);
                        Thread.Yield();
                        iDet = iDoc.Details.FirstOrDefault(iv => iv.Name == randomValueName);
                        if (iDet == null)
                        {
                            iDet      = session.NewEntity <IDocDetail>();
                            iDet.Doc  = iDoc;
                            iDet.Name = randomValueName;
                            iDoc.Details.Add(iDet); //to correctly calculate total
                        }
                        iDet.Value = rand.Next(10);
                        iDoc.Total = iDoc.Details.Sum(v => v.Value);
                        Thread.Yield();
                        session.SaveChanges();

                        var entSession = (Vita.Entities.Runtime.EntitySession)session;
                        if (entSession.CurrentConnection != null)
                        {
                            Debugger.Break(); //check that connection is closed and removed from session
                        }
                        break;

                    case 2: //delete if exists
                        session.LogMessage("\r\n----------------- Delete value ---------------------");
                        //Note: deletes do not throw any errors - if record does not exist (had been just deleted), stored proc do not throw error
                        iDoc = session.GetEntity <IDoc>(randomDocId, writeOptions);
                        Thread.Yield(); //allow others mess up
                        iDet = iDoc.Details.FirstOrDefault(iv => iv.Name == randomValueName);
                        if (iDet != null)
                        {
                            session.DeleteEntity(iDet);
                            iDoc.Details.Remove(iDet);
                            iDoc.Total = iDoc.Details.Sum(v => v.Value);
                        }
                        Thread.Yield();
                        session.SaveChanges(); // even if there's no changes, it will release lock
                        break;

                    case 3:
                    case 4: //read and check total
                        session.LogMessage("\r\n----------------- Loading doc ---------------------");
                        iDoc = session.GetEntity <IDoc>(randomDocId, readOptions);
                        Thread.Yield(); //to let other thread mess it up
                        var valuesSum = iDoc.Details.Sum(v => v.Value);
                        if (valuesSum != iDoc.Total)
                        {
                            session.LogMessage("!!!! Sum error: Doc.Total: {0}, Sum(Value): {1}", iDoc.Total, valuesSum);
                            Interlocked.Increment(ref _sumErrorCount);
                        }
                        Thread.Yield();
                        session.ReleaseLocks();
                        break;
                    }//switch
                    WriteLog(session);
                } catch (Exception ex) { //most will be UniqueIndexViolation
                    Debug.WriteLine(ex.ToLogString());
                    System.Threading.Interlocked.Increment(ref _updateErrorCount);
                    if (session != null)
                    {
                        WriteLog(session);
                        var log = session.Context.LocalLog.GetAllAsText();
                        Debug.WriteLine(log);
                        var entSession = (Vita.Entities.Runtime.EntitySession)session;
                        if (entSession.CurrentConnection != null)
                        {
                            entSession.CurrentConnection.Close();
                        }
                    }
                }
            } //for i
        }     //method
Ejemplo n.º 8
0
        public static void CreateUpdatePopularServers(IEntitySession session)
        {
            // Windows Live
            CreateOrUpdateServer(session, "WindowsLive",
                                 OAuthServerOptions.TokenReplaceLocalIpWithLocalHost,
                                 "https://www.live.com/",
                                 "https://login.live.com/oauth20_authorize.srf",
                                 "https://login.live.com/oauth20_token.srf",
                                 "https://login.live.com/oauth20_token.srf", // refresh URL, same as access token URL
                                 null,                                       //no revoke URL
                                 "wl.basic wl.emails wl.photos wl.offline_access wl.signin",
                                 "https://msdn.microsoft.com/en-us/library/hh243647.aspx",
                                 "https://apis.live.net/v5.0/me",
                                 "id");

            // Google
            // Specifics: Refresh token is returned only in the first request for access token
            CreateOrUpdateServer(session, "Google",
                                 OAuthServerOptions.OpenIdConnect | OAuthServerOptions.RevokeUseGetNoClientInfo,
                                 "http://www.google.com",
                                 "https://accounts.google.com/o/oauth2/v2/auth",
                                 "https://www.googleapis.com/oauth2/v4/token",
                                 "https://www.googleapis.com/oauth2/v4/token",
                                 "https://accounts.google.com/o/oauth2/revoke", //?token={token}", //revoke
                                 "profile email",
                                 "https://developers.google.com/identity/protocols/OAuth2WebServer",
                                 "https://www.googleapis.com/plus/v1/people/me",
                                 "id");

            // Facebook
            // TODO: Investigage; looks like FB supports id_token (like in OpenIdConnect), but requires some twists
            // investigate why currently does not return id_token
            CreateOrUpdateServer(session, "Facebook",
                                 OAuthServerOptions.TokenUseGet | OAuthServerOptions.OpenIdConnect
                                 | OAuthServerOptions.TokenReplaceLocalIpWithLocalHost,
                                 "http://www.facebook.com",
                                 "https://www.facebook.com/dialog/oauth",
                                 "https://graph.facebook.com/v2.3/oauth/access_token",
                                 null,
                                 null, // no revoke permissions; FB says you can use 'DELETE /{user-id}/permissions', but it's out of the line of oauth
                                 "public_profile email",
                                 "https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow",
                                 "https://graph.facebook.com/v2.5/me", //basic profile
                                 "id");

            // LinkedIn. Specifics:
            //   1. LinkedIn uses Get for access token endpoint (OAuth2 spec requires POST)
            CreateOrUpdateServer(session, "LinkedIn",
                                 OAuthServerOptions.TokenUseGet,
                                 "http://www.linkedin.com",
                                 "https://www.linkedin.com/oauth/v2/authorization",
                                 "https://www.linkedin.com/oauth/v2/accessToken",
                                 null, //refresh
                                 null, // revoke
                                 "r_basicprofile r_emailaddress rw_company_admin w_share",
                                 "https://developer.linkedin.com/docs/oauth2",
                                 "https://api.linkedin.com/v1/people/~?format=json",
                                 "id");

            // Fitbit.
            //  1. Access token endpoint requries authorization header which is Base64 encoded 'clientid:clientsecret'
            CreateOrUpdateServer(session, "Fitbit",
                                 OAuthServerOptions.ClientInfoInAuthHeader,
                                 "https://www.fitbit.com",
                                 "https://www.fitbit.com/oauth2/authorize",
                                 "https://api.fitbit.com/oauth2/token",
                                 "https://api.fitbit.com/oauth2/token",  // refresh token
                                 "https://api.fitbit.com/oauth2/revoke", // ?token={token}", //revoke, use POST
                                 "activity heartrate location nutrition profile settings sleep social weight",
                                 "https://dev.fitbit.com/docs/oauth2/",
                                 "https://api.fitbit.com/1/user/-/profile.json",
                                 "encodedId");

            // Jawbone
            CreateOrUpdateServer(session, "Jawbone",
                                 OAuthServerOptions.TokenUseGet,
                                 "https://jawbone.com/",
                                 "https://jawbone.com/auth/oauth2/auth",
                                 "https://jawbone.com/auth/oauth2/token",
                                 "https://jawbone.com/auth/oauth2/token",
                                 null, //revoke
                                 "basic_read extended_read location_read friends_read mood_read mood_write move_read move_write " +
                                 "sleep_read sleep_write meal_read meal_write weight_read weight_write " +
                                 "generic_event_read generic_event_write heartrate_read",
                                 "https://jawbone.com/up/developer/authentication",
                                 "https://jawbone.com/nudge/api/v.1.1/users/@me",
                                 "user_xid");

            // Yahoo. Specifics: it is impossible to test - it does not allow localhost as redirect URL.
            // There are fancy hacks/workarounds (creating localtest.me in hosts file) - but that's too much.
            // It is disabled here, but you can enable it if you need Yahoo

            /*
             * CreateOrUpdateServer(session, "Yahoo",
             * OAuthServerOptions.OpenIdConnect | OAuthServerOptions.RequestTokenClientInfoInAuthHeader
             | OAuthServerOptions.TokenReplaceLocalIpWithLocalHost,
             | "http://www.yahoo.com",
             | "https://api.login.yahoo.com/oauth2/request_auth",
             | "https://api.login.yahoo.com/oauth2/get_token",
             | "https://api.login.yahoo.com/oauth2/get_token",
             | "sdps-r mail-r",
             | "https://developer.yahoo.com/oauth2/guide/",
             | "https://social.yahooapis.com/v1/user/abcdef123/profile?format=json");
             */


            session.SaveChanges();
        }
Ejemplo n.º 9
0
        public static void CreateUpdatePopularServers(IEntitySession session)
        {
            // Windows Live
              CreateOrUpdateServer(session, "WindowsLive",
            OAuthServerOptions.TokenReplaceLocalIpWithLocalHost,
            "https://www.live.com/",
            "https://login.live.com/oauth20_authorize.srf",
            "https://login.live.com/oauth20_token.srf",
            "https://login.live.com/oauth20_token.srf",  // refresh URL, same as access token URL
            "wl.basic wl.emails wl.photos wl.offline_access wl.signin",
            "https://msdn.microsoft.com/en-us/library/hh243647.aspx",
            "https://apis.live.net/v5.0/me",
            "id");

              // Google
              // Specifics: Refresh token is returned only in the first request for access token
              CreateOrUpdateServer(session, "Google",
              OAuthServerOptions.OpenIdConnect,
              "http://www.google.com",
              "https://accounts.google.com/o/oauth2/v2/auth",
              "https://www.googleapis.com/oauth2/v4/token",
              "https://www.googleapis.com/oauth2/v4/token",
              "profile email",
              "https://developers.google.com/identity/protocols/OAuth2WebServer",
              "https://www.googleapis.com/plus/v1/people/me",
              "id");

              // Facebook
              // TODO: Investigage; looks like FB supports id_token (like in OpenIdConnect), but requires some twists
              // investigate why currently does not return id_token
              CreateOrUpdateServer(session, "Facebook",
            OAuthServerOptions.TokenUseGet | OAuthServerOptions.OpenIdConnect | OAuthServerOptions.TokenReplaceLocalIpWithLocalHost,
            "http://www.facebook.com",
            "https://www.facebook.com/dialog/oauth",
            "https://graph.facebook.com/v2.3/oauth/access_token",
            null,
            "public_profile email",
            "https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow",
            "https://graph.facebook.com/v2.5/me",
            "id");

              // LinkedIn. Specifics:
              //   1. LinkedIn uses Get for access token endpoint (OAuth2 spec requires POST)
              CreateOrUpdateServer(session, "LinkedIn",
            OAuthServerOptions.TokenUseGet,
            "http://www.linkedin.com",
            "https://www.linkedin.com/oauth/v2/authorization",
            "https://www.linkedin.com/oauth/v2/accessToken",
            null,
            "r_basicprofile r_emailaddress rw_company_admin w_share",
            "https://developer.linkedin.com/docs/oauth2",
            "https://api.linkedin.com/v1/people/~?format=json",
            "id");

              // Fitbit.
              //  1. Access token endpoint requries authorization header which is Base64 encoded 'clientid:clientsecret'
              CreateOrUpdateServer(session, "Fitbit",
              OAuthServerOptions.RequestTokenClientInfoInAuthHeader,
              "https://www.fitbit.com",
              "https://www.fitbit.com/oauth2/authorize",
              "https://api.fitbit.com/oauth2/token",
              "https://api.fitbit.com/oauth2/token",  // refresh token
              "activity heartrate location nutrition profile settings sleep social weight",
              "https://dev.fitbit.com/docs/oauth2/",
              "https://api.fitbit.com/1/user/-/profile.json",
              "encodedId");

            // Jawbone
            CreateOrUpdateServer(session, "Jawbone",
              OAuthServerOptions.TokenUseGet,
              "https://jawbone.com/",
              "https://jawbone.com/auth/oauth2/auth",
              "https://jawbone.com/auth/oauth2/token",
              "https://jawbone.com/auth/oauth2/token",
              "basic_read extended_read location_read friends_read mood_read mood_write move_read move_write " +
            "sleep_read sleep_write meal_read meal_write weight_read weight_write " +
            "generic_event_read generic_event_write heartrate_read",
              "https://jawbone.com/up/developer/authentication",
              "https://jawbone.com/nudge/api/v.1.1/users/@me",
              "user_xid");

              // Yahoo. Specifics: it is impossible to test - it does not allow localhost as redirect URL.
              // There are fancy hacks/workarounds (creating localtest.me in hosts file) - but that's too much.
              // It is disabled here, but you can enable it if you need Yahoo
              /*
              CreateOrUpdateServer(session, "Yahoo",
            OAuthServerOptions.OpenIdConnect | OAuthServerOptions.RequestTokenClientInfoInAuthHeader
             | OAuthServerOptions.TokenReplaceLocalIpWithLocalHost,
            "http://www.yahoo.com",
            "https://api.login.yahoo.com/oauth2/request_auth",
            "https://api.login.yahoo.com/oauth2/get_token",
            "https://api.login.yahoo.com/oauth2/get_token",
            "sdps-r mail-r",
            "https://developer.yahoo.com/oauth2/guide/",
            "https://social.yahooapis.com/v1/user/abcdef123/profile?format=json");
              */

              session.SaveChanges();
        }
Ejemplo n.º 10
0
        // If necessary, can be called explicitly outside of migrations to unencrypt existing values
        public static void UnencryptTokens(IEntitySession session)
        {
            var config = session.Context.App.GetConfig <OAuthClientSettings>();
            var errLog = session.Context.App.ErrorLog;
            // Unencrypt client secret in remote server accounts - should be a smaill table
            var accts = session.EntitySet <IOAuthRemoteServerAccount>().Where(a => a.ClientSecret_Id != null).Take(100).ToList();

            foreach (var acct in accts)
            {
                var ed = session.GetEntity <IEncryptedData>(acct.ClientSecret_Id);
                if (ed == null)
                {
                    continue;
                }
                acct.ClientSecret    = ed.DecryptString(config.EncryptionChannel);
                acct.ClientSecret_Id = null;
            }
            session.SaveChanges();

            // Tokens - might be big table, process in batches
            int batchSize = 200;
            int skip      = 0;

            try {
                var tokenRecs = session.EntitySet <IOAuthAccessToken>()
                                .Where(tr => tr.AccessToken == null).OrderBy(tr => tr.RetrievedOn)
                                .Skip(skip).Take(batchSize).ToList();
                skip += batchSize;
                if (tokenRecs.Count == 0)
                {
                    return;
                }
                //preload all EncryptedValue records
                var encrIds1 = tokenRecs.Where(tr => tr.AccessToken_Id != null).Select(tr => tr.AccessToken_Id.Value).ToList();
                var encrIds2 = tokenRecs.Where(tr => tr.RefreshToken_Id != null).Select(tr => tr.RefreshToken_Id.Value).ToList();
                encrIds1.AddRange(encrIds2);
                var encrRecs = session.EntitySet <IEncryptedData>().Where(ed => encrIds1.Contains(ed.Id));
                foreach (var tr in tokenRecs)
                {
                    //Access token
                    var eId = tr.AccessToken_Id;
                    if (eId != null && eId.Value != Guid.Empty)
                    {
                        var ed = session.GetEntity <IEncryptedData>(eId.Value); //should be preloaded
                        if (ed != null)
                        {
                            tr.AccessToken    = ed.DecryptString(config.EncryptionChannel);
                            tr.AccessToken_Id = null;
                        }
                    }
                    // Refresh token
                    eId = tr.AccessToken_Id;
                    if (eId != null && eId.Value != Guid.Empty)
                    {
                        var ed = session.GetEntity <IEncryptedData>(eId.Value); //should be preloaded
                        if (ed != null)
                        {
                            tr.RefreshToken    = ed.DecryptString(config.EncryptionChannel);
                            tr.RefreshToken_Id = null;
                        }
                    }
                } //foreach tr
                session.SaveChanges();
            } catch (Exception ex) {
                if (errLog != null)
                {
                    errLog.LogError(ex, session.Context);
                }
                if (!SuppressMigrationErrors)
                {
                    throw;
                }
            }
        } //method