Friday, October 25, 2013

Access Denied on Orchard CMS theme files on IIS 7

Today I'm working on moving an Orchard CMS site from an IIS6 server to an IIS7 server.  I copied the folder over to its new home and used "Add Application" to set the folder up.  I added the Application Pool user (use "IIS AppPool\AppPoolName" as the user in the security dialog -- see http://stackoverflow.com/questions/7334216/iis7-permissions-overview-applicationpoolidentity) and voila, everything worked!  Well -- almost everything -- none of my themes' css were showing up.

In the browser network tab I could see that all of the css files were getting a 302 error -- "Access Denied".  Strange!

I found that in the IIS7 Authentication panel, the Anonymous Authentication was set to use the default IIS "IUSR" instead of my application pool identity.  The dialog:

Changing it to "Application pool identity" did the trick!


Friday, August 2, 2013

Crashmapper - creative way to use GIS

Here's a guy who data-scraped NYC provided PDF's in order to have the ability to provide an interactive heat map of New York accidents!  Pretty cool idea and quite a determination.

http://nyc.crashmapper.com/#about

Friday, March 22, 2013

THE GOODs LIFE

In the "old days" (and I mean since Aristotle and his Ethics) kids grew up thinking and wondering about how to live a "good life".  Apparently, everyone only gets one life and since it seems rather a waste to lay down in the end and say, "Well, that was a really rotten life!" and so we might ask: what can be done to have a good one?  That was the kind of question people used to talk about.

Lately (and I mean in the last 70 years or so) it seems everyone has finally agreed on the answer.  The consensus is so complete that almost everyone has entirely stopped asking the question, "How do you live a good life?" because there is no reason to talk about a question that everyone already knows the answer to.  It just isn't all that interesting anymore since everyone agrees on what the "good life" is and how to attain it.

Just ask yourself, "What is the one thing I need to have an awesome life?"

Your answer is probably something like, "Win the lottery!"  Of course, it isn't so much the money you'd be excited about, though for some people, the image of laying on piles of $100 bills as big as a mattress would be downright thrilling.  What you want to win the lottery for is the "stuff" you could get.  It would change your life for the better, wouldn't it?  There's no question!  Of course it would!

If we all know money can buy you the good life, then there's really nothing more to discuss about what the good life actually is or how one can go about living it.  The more important thing is to get busy making some money.

If the question of "How do I live a good life?" has the answer: "Buy it!" then we would talk about things like:

  • Do you have a good job?
  • What kind of phone do you have?
  • How big is your house?
  • What kind of car do you drive?
  • What clothes do you wear?
  • What school did you go to?

"How to help your child be successful" would be all about "How to help your kid get a good job" because, naturally, a better job is more money is more of the good life.

Now, we all would say we know that "money can't buy happiness" (or love for that matter) but when we think about what we actually spend ourselves on and how we measure each other, our actions tell a different story.  Our actions always betray our true beliefs.

The reason we don't talk about the good life is because we can no longer define "good" as a culture.  The word "good" is dangerous ground.  If there is "good" then there must be "bad" and now you're making some kind of moral statement and "Who are you to say your moral system is superior to mine?" becomes the immediate postmodern reaction.  

But that that leaves us with the best we can have is a "pleasureable life" and we're sold the marketing storyboard as the means to attain it.  We just need to put a few more dollars on our credit card and we can get that priceless moment that will truly make us happy and give us the good life, in the non-moral, un-philosophical, and rather un-satisfying sense.

We've given up pursuit of the "good life" for the "goods life".  But I don't think we got a very good deal.

Tuesday, March 12, 2013

The Alternative Kingdom

"I resolved to regard nothing in your midst except Jesus Christ and him crucified." 1 Cor 2:2

"From now on we disregard all common human distinctions between people, and even though we have known Christ in human terms, we no longer do so.  So if anyone is 'in Christ' they are a new type of creation, where the old categories drop away and the individual emerges in a new order." 2 Cor 5:16-17

The Kingdom of God is a new order where a person surrenders to God's economy, his "measuring system".  The scorecard is different.

All members of the Kingdom are rescuees, smoking equally of the fires of hell.  Once condemned by our mutinous actions against the True King, now all surrenderees are beloved and blessed and related as one family.  Once serving only themselves, now Kingdom citizens are united together for service to bring the King God glory by rescuing still more lowly, condemned trespassers.

The rich and powerful, religious and smart -- all are welcome but few come.  Their labels and advantages prove too much to overcome -- their place in the fortresses of the shadowlands is too secure.  We look past the pecking order and are not impressed by the attainments of the glittering images they raise up -- no power or glory do they wield in our New Country.  In the Kingdom of God, a homeless man is the saviour of the world!  Those considered as nothing -- "white trash", and everything else -- these find "eternity present" standing open before them.  "The meek inherit the earth" -- because it is they who would find it gain to abandon all to enter the Kingdom.

The religious types often stand in their way, dividing doctrine and law.  Or worse these days: offering a toxic cocktail called "Consumer-God". "'Consumer-God' will bless you with prosperity and give you more of "The Good Life" for the low price of only 10%!" No wonder "The Kingdom of God has suffered violence and the violent take it by force": it is from the likes of these it must be wrested!

We cannot admire their degrees and fine sounding arguments.  We cannot seek their approval or measure anything by their standards.  The Kingdom measures according to Christ: What have you done with the carpenter from Nazareth?  Look to the ones who are hungry and needy--trying to keep their dignity in a world where they score poorly by the measures we once used.

Instead, now, we know the Kingdom of God is open to them -- they can live as princes and princesses, adopted by the King!  He fills us with joy, peace, patience, kindness, goodness, faithfulness, self-control.  New Life! A set-free life.  But only available by crucifixion -- our lives -- the old ones -- die alongside Christ.  Our old letters of recommendation are gone.  The old amassments of wealth -- gone.  We abandon them all for a new currency.  Now we traffic in Joy.

When our shiny eyes open on the world blazing with new life, we see things clear.  The pushing and jostling is no longer for us.  The pleasures and sensualities are empty.  The goals and attainments we once gave ourselves to, that looked so worthy, have become unrecognizable -- their delight drained dry.

A new lamp is lit in our heart, burning from the inside.  The Spirit of the Living God, having given us new eyes to see.  Now we can love our neighbor and be perfect as the energy He imparts to live His Kingdom Dream flows into our lives.  We look on those poor and broken no longer with pity and angst but with compassion, knowing they are closer to the kingdom for it belongs to them and all who will reach out their hands for the true freedom offered by the Messiah.  We go forth now into the world but living in the Kingdom of God.  Now we are on mission, looking for the lowliest that we might lift their eyes to the heavens.  We: beggars who know where the bread is.  And so we share.

Monday, March 11, 2013

Statistics functions in Access 2010

I don't know why, but there are quite a number of statistics functions that are in Microsoft Excel that are not included in Microsoft Access. Some of these are CONFIDENCE.T, SKEW, KURT, MEDIAN, MODE, etc. I found two options to get use these functions in Access 2010.

Option 1: Add a module and create some domain functions

This is the most attractive to me because it is fast and doesn't require using the external Excel worksheet object. Patrick G. Matthews posted excellent instructions and code here: http://www.experts-exchange.com/Microsoft/Development/MS_Access/A_2529-Median-Mode-Skewness-and-Kurtosis-in-MS-Access.html.

After creating my module and pasting in his source code, I created a query:

SELECT DMode("length","Numbers") as Mode;

This gives me the MODE of the column "length" in the table "Numbers". You can pass along a where clause as necessary.

Option 2: Call out to the Excel worksheet object.

Unfortunately, I couldn't find any snazzy VBA code from Patrick to give me the CONFIDENCE.T function for the excel worksheet I was translating for an Access report. I did, however, find that I could write a function like this:
Function fConfidence(a_alpha As Double, a_count As Long, a_stdev As Double)

    Dim objExcel As Object
    
    Set objExcel = CreateObject("Excel.Application")
    objExcel.Visible = False

    fConfidence = objExcel.WorksheetFunction.CONFIDENCE_T(a_alpha, a_stdev, a_count)
    
    Set objExcel = Nothing
    

End Function

This worked fine once I realized that I needed to call "CONFIDENCE_T" and not "CONFIDENCE.T" (the error I was getting said that the function didn't have the right number of parameters. Not very helpful). Thanks to http://msdn.microsoft.com/en-us/library/office/ff838173.aspx I found the name difference.

Now I can call a query to get the various statistics information I need for the report:

SELECT DMedian("length","Numbers") AS Median, DAvg("length","Numbers") AS Average,
 DKurtosis("length","Numbers") AS Kurtosis, DSkewness("length","Numbers") AS Skewness,
 DMode("length","Numbers") AS Mode, DMax("length","Numbers") AS [Max], DMin("length","Numbers") AS [Min],
 DStDev("length","Numbers") AS StdDev, DCount("length","Numbers") AS [Count], StdDev/Sqr(Count) AS StdError,
 DSum("length","Numbers") AS [Sum], Max-Min AS Range, DVar("length","Numbers") AS SampleVariance,
 fConfidence(0.05, Count, StdDev) As ConfidenceLevel
Handy!

Friday, February 8, 2013

Using Active Directory authentication in MVC4

We bought the new book Pro ASP.NET MVC4 by Adam Freeman, which is a great book and very well written.  However, in the authentication section, he assumes (probably rightly) most folks will be writing applications for the broader internet and want to implement a stand-alone means of authentication (probably wrongly).  It seems to me that these days you deal your site out unless people can single sign-on in some form or fashion using an existing google or facebook account or other account.  Anyhow, for our Project Tracker application, we're all in-house and Adam didn't have any explanation for setting up AD Authentication, so this is how it is done.

First, you'll need to find the LDAP url you'll use to communicate with the windows domain server that hosts your Active Directory accounts.  If you don't know it, open a command prompt and type:

gpresult /r

That'll give you a bunch of information related to your account and will include a line like:

USER SETTINGS
--------------
    CN=Ken Burcham,OU=Users,OU=Geographic Information Systems,OU=Office of Infor
mation Technology,OU=Administration,OU=CTUIR,DC=mailcomm,DC=ctuir,DC=com
    Last time Group Policy was applied: 2/7/2013 at 1:19:28 PM
    Group Policy was applied from:      CTUIR-DCX.mailcomm.ctuir.com
    Domain Name:                        MAILCOMM


From this, you'll need to construct an LDAP connection string and add it to the connectionStrings section of  your web.config.  For me, was something like this:



<add name="ADConnectionString" connectionString ="LDAP://CTUIR-DCX.mailcomm.ctuir.com:389/DC=mailcomm,DC=ctuir,DC=com" />


Note that the default port is 389 but if you are connecting from external to your network you'll certainly want to connect using SSL which for LDAP is port 636. Also note that the order of the elements is important. :) You'll also need to add the following section to your web.config to enable the ADMembershipProvider:

<membership defaultProvider="ADMembershipProvider">
      <providers>
        <clear/>
        <add name="ADMembershipProvider" type="System.Web.Security.ActiveDirectoryMembershipProvider" connectionStringName="ADConnectionString" attributeMapUsername="sAMAccountName"/>
      </providers>
    </membership>

Next, configure your application to handle authentication by adding this to your Global.asax.cs file:

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new HandleErrorAttribute());
            filters.Add(new System.Web.Mvc.AuthorizeAttribute());
        }

Now you need to connect the hoses in your AccountController. This section is mostly following the instructions posted by Ricardo Sanchez here: How to secure your asp net mvc application

[Authorize]
    public class AccountController : Controller
    {
        //
        // GET: /Account/Login

        [AllowAnonymous]
        public ActionResult Login(string returnUrl)
        {
            ViewBag.ReturnUrl = returnUrl;
            return View();
        }

        //
        // POST: /Account/Login

        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult Login(LoginModel model, string returnUrl)
        {
            if (ModelState.IsValid)
            {
                if(Membership.ValidateUser(model.UserName, model.Password))
                {
                    FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
                    if(Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
                        && !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
                    {
                        return Redirect(returnUrl);
                    }
                    else
                    {
                        return RedirectToAction("Index","Home");
                    }
                }
                else
                {
                    ModelState.AddModelError("","The user name or password provided is incorrect.");
                }
            }
            
            return View(model);
        }

        //
        // POST: /Account/LogOff

        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult LogOff()
        {
            FormsAuthentication.SignOut();

            return RedirectToAction("Index", "Home");
        }

That's about it. One last thing, I used this at the top of my _loginPartial.cshtml which worked nicely to display the currently logged in user:

@(User.Identity.IsAuthenticated ? "Hello, " + User.Identity.Name : "Not logged in.")

Well, I didn't find all of this in one place, so hopefully it'll help someone else! :)

Wednesday, February 6, 2013

Technical information for ExchangeNetwork and WQX

When working on my WQXUploader utility, the STORET and ExchangeNetwork helpdesk provided me a number of links that were helpful, as well as some I found on my own.  Here is a collection of technical information that was helpful for reference:

The top level of the ExchangeNetwork information:
 
 
Two open source projects that were great for code browsing:
This was a helpful example in getting my Visual Studio project started:
 
These were great for details:
 

Tuesday, February 5, 2013

Uploading multiple water temperature files to CDX/WQX Exchange Network

Upload tool for submitting multiple files at once to the WQX

We have more than 100 xml files of water temperature to upload to the WQX water quality exchange, which is an Axis endpoint located here:

Water Quality Exchange (WQX)

Node 2.1 Test – https://testngn.epacdxnode.net/ngn-enws20/services/NetworkNode2ServiceConditionalMTOM
Node 2.1 Production – https://cdxnodengn.epa.gov/ngn-enws20/services/NetworkNode2ServiceConditionalMTOM
Node 1.1 Test – https://testngn.epacdxnode.net/cdx-enws10/services/NetworkNodePortType_V10
Node 1.1 Production – https://cdxnodengn.epa.gov/cdx-enws10/services/NetworkNodePortType_V10

Getting the account(s) setup

WQXWeb account

First I had to get my WQX Web account setup.  I contacted the STORET folks who setup my account by following these instructions: http://www.epa.gov/storet/wqx/wqxweb.html.  I emailed their helpdesk with the information they asked for and they setup my account. Then I could login to http://cdx.epa.gov and view our organization's data as well as manually upload individual files.

Rather than upload the files individually through the web interface, I wanted to upload the files in bulk.  On the getting started with WQX page (http://www.epa.gov/storet/wqx/wqx.html) they suggest using the Windsor Node Client Lite 2 software (http://www.windsorsolutions.biz/nodeclient/disclaimer.html) so I downloaded it and followed the instructions on the getting started page for getting the endpoints configured.  But I ran into a problem.

NAAS Account

As it turns out, the username/password that STORET gives you is not a NAAS account.  You need to have a NAAS account in order to submit data using the endpoints above, which includes submitting files using the Node Client Lite 2 software.

To setup a NAAS account, I needed to email nodehelpdesk@epacdx.net and request a NAAS account that is linked to my STORET account and provide them with my STORET account id and organization id.  They contacted the STORET helpdesk and asked for their approval to set me up.  Once I was approved, they sent me my new, shiny NAAS account.

Using the new NAAS credentials to login with the Node Client Lite 2 software, I was able to submit a file to the test node.  Once that was successful, I needed to contact the nodehelpdesk@epacdx.net guys again and give them the successfully submitted transaction id so that they could verify that everything about my file was good and then they enabled me for submitting to the production node.

Now I thought I was all set!  I selected a bunch of files through the Node Client Lite user interface but received an error that the endpoint expected one file but received many.  I asked the helpdesk about it and they informed me that I could only send one file at a time.  Bummer!  So I decided to write my own client that would upload my files.  We want to automate this whole process in the future anyway, so I thought I'd just whip up a connector.

Writing WQXUpload tool

These days, I'm writing in c# using Visual Studio 2012.  So I fired it up and created a new project.  Then I went to PROJECT -> add service reference, clicked "Advanced" button and then "Add Web Reference".  I pasted the URL for the endpoint I'm using (https://testngn.epacdxnode.net/cdx-enws10/services/NetworkNodePortType_V10 I'd definitely start with the test url) and then let it build my SOAP connector class automagically.

Then here is my code for the uploader.  Enjoy!

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ConsoleApplication1.gov.epa.cdxnodengn;
using ConsoleApplication1.gov.epa.cdx;
using System.IO;

namespace ConsoleApplication1
{
    class WQXUploader
    {
        private static string defaultFilePath = "."; //all .xml files in this directory will be uploaded.

        private static NetworkNode node;
        private static string token;
        private static int numFilesUploaded = 0;


        static int Main(string[] args)
        {

            if (args.Length < 2 )
            {
                log("Syntax: WQXUpload   [path] (default path is current directory)");
                waitForKeypress();
                return 1;
            }

            //setup our passed in credentials.
            string username = args[0];
            string password = args[1];

            //if they call us with a path then set it here.
            string filePath = defaultFilePath;
            if (args.Length == 3)
            {
                filePath = args[2];
            }

            //done with args now get rolling...
            log("Starting WQX Uploader.");
            Inititialize();
            Authenticate(username, password);

            //which files to upload?
            List filesToUpload = getFilesToUpload(filePath);

            //submit each file.
            foreach (var file in filesToUpload)
            {
                //log("Uploading file: " + file);
                submitFile(token, file, filePath);
                numFilesUploaded++;
            }

            Cleanup();
            log("WQX upload is finished. We uploaded "+ numFilesUploaded + " files." );
            waitForKeypress();

            return 0;
        }

        /**
         * Gets a list of files from the filepath that we will upload
         * @return List files to upload
         */ 
        private static List getFilesToUpload(string filePath)
        {
            log("Using path: " + filePath);

            List files = new List();

            DirectoryInfo di = new DirectoryInfo(filePath);
            var directories = di.GetFiles("*.xml", SearchOption.TopDirectoryOnly);

            foreach (FileInfo file in directories)
            {
                files.Add(file.Name);
            }

            return files;
        }

        /*
         * Wait for a keypress -- just to keep the console open.
         */ 
        private static void waitForKeypress()
        {
            Console.WriteLine("Press ESC to exit");
            do
            {
                while (!Console.KeyAvailable)
                {
                    // Do something
                }
            } while (Console.ReadKey(true).Key != ConsoleKey.Escape);
        }

        /**
         * Submits file to WQX
         * @return transactionId of the submission.
         */ 
        private static string submitFile(string token, string fileToUpload, string filePath)
        {
            NodeDocument doc = new NodeDocument();

            byte[] buffer = File.ReadAllBytes(filePath + "\\" + fileToUpload);

            doc.name = fileToUpload;
            doc.type = "xml";
            doc.content = buffer; 

            NodeDocument[] docToSend = { doc };

            String transactionId = node.Submit(token, "", "WQX", docToSend);

            log(fileToUpload + " " + transactionId);
            //logStatus(transactionId);

            return transactionId;

        }

        /*
         * Outputs the status of the transaction to the log.
         */ 
        private static void logStatus(string transactionId)
        {
            log(" Current status: " + node.GetStatus(token, transactionId));
        }

        /*
         * Writes the message to the log
         */ 
        private static void log(string msg)
        {
            Console.WriteLine(msg);
        }

        /*
         * Initializes the NetworkNode connection
         */ 
        private static void Inititialize()
        {
            node = new NetworkNode();
        }

        /**
         * Performs any cleanup necessary
         */ 
        private static void Cleanup()
        {
            node.Dispose();
        }

        /*
         * Connects and authenticates us with the endpoint, fetching our token necessary for other functions.
         * @return string token
         */ 
        private static string Authenticate(String username, String password)
        {
            log("Connecting and authenticating...");
            token = node.Authenticate(username, password, "password");
            log("Success! Security token: " + token);
            return token;
        }
    }
}